| 1 | A very brief overview of Sapphire |
| 2 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
| 3 | |
| 4 | _____________________________________________________________________________ |
| 5 | |
| 6 | IS THIS ALL THERE IS? |
| 7 | |
| 8 | |
| 9 | The Grand Plan was to write a really good library, sell some spiffy |
| 10 | applications which used it, and then sell the library. This |
| 11 | spectacularly failed to work, mainly because we had no ideas for killer |
| 12 | applications. The result is Sapphire. |
| 13 | |
| 14 | Since we'd planned to write some big applications before releasing |
| 15 | Sapphire to anyone else, we took a rather relaxed attitude to |
| 16 | documentation. There is some LaTeX documentation lying around, but |
| 17 | mostly it's initial versions of things, with big gaps where less |
| 18 | interesting text should be filled in later. In short, it's no use to |
| 19 | anyone. |
| 20 | |
| 21 | On the other hand, we realised that a library of this complexity with no |
| 22 | documentation at all would be a total disaster, so all the header files |
| 23 | are profusely commented, mostly to the standard of a final-copy |
| 24 | reference manual. |
| 25 | |
| 26 | This text file is intended as a brief hacker's eye view of how to write |
| 27 | Sapphire applications, and how some of the more complicated and/or |
| 28 | interesting bits work. |
| 29 | |
| 30 | _____________________________________________________________________________ |
| 31 | |
| 32 | ABOUT THE AUTHORS |
| 33 | |
| 34 | |
| 35 | Sapphire was designed and written, over the course of a couple of years, |
| 36 | by Mark Wooding and Tim `Tycho' Armes. Each source file usually bears |
| 37 | the initials (`MDW' or `TMA') of the person mainly responsible for |
| 38 | writing it. In some cases, this is misleading. For example, although |
| 39 | the `msgs' code for translating message tokens into strings is labelled |
| 40 | `MDW', the first two versions of this file were written by Tim: I took |
| 41 | over later because I rewrote it to kill the bugs. Similarly, the fact |
| 42 | that one of us wrote a source file doesn't tell you who designed it. For |
| 43 | example, the `menu' code was entirely written by Tim, although we both |
| 44 | worked on the design for several days beforehand. |
| 45 | |
| 46 | _____________________________________________________________________________ |
| 47 | |
| 48 | A HISTORY LESSON |
| 49 | |
| 50 | |
| 51 | Sapphire is an attempt to learn from the experience we both had with |
| 52 | STEEL. STEEL (Straylight's Extensive Event-driven Library) is a |
| 53 | more-or-less complete re-write of RISC_OSLib, and although it had given |
| 54 | up being call-compatible, and had diverged quite considerably by the |
| 55 | release of Desktop C, it suffered because of the limitations of its |
| 56 | structure. All the original non-RISC_OSLib code had been written by me, |
| 57 | in a rather ragged way. |
| 58 | |
| 59 | The name Sapphire comes from the old ITV sci-fi series `Sapphire and |
| 60 | Steel', obviously. It's a rotten joke, but that's what you get when you |
| 61 | think up names for libraries in a pub. The idea was that two of us |
| 62 | could do much better than either acting alone, by bouncing ideas between |
| 63 | each other until we sorted out the problems. We threw out any thought |
| 64 | of compatibility with any existing software, and any hope of interfacing |
| 65 | a high-level language. Then we started coding. |
| 66 | |
| 67 | _____________________________________________________________________________ |
| 68 | |
| 69 | INFLUENCES |
| 70 | |
| 71 | |
| 72 | The design of Sapphire has been influenced by looking at lots of other |
| 73 | pieces of software. A lot of structure is based on RISC_OSLib: STEEL |
| 74 | was derived from RISC_OSLib, and it did a lot of things right. Another |
| 75 | particularly strong influence is Computer Concepts' Advanced Box |
| 76 | Interface (ABI) as used in Impression and ArtWorks. (I even went as far |
| 77 | as buying the ArtWorks SDK more-or-less to get my hands on some ABI |
| 78 | documentation.) There was an influence from what I perceived to be `the |
| 79 | way OS/2's Presentation Manager does it', although this wasn't always |
| 80 | positive. |
| 81 | |
| 82 | _____________________________________________________________________________ |
| 83 | |
| 84 | SUPPORTED LANGUAGES |
| 85 | |
| 86 | |
| 87 | Sapphire supports ARM assembler. That's about it. One of the last |
| 88 | Sapphire-related projects was interfacing C to Sapphire; this is still |
| 89 | mostly incomplete. The basic job of making C code call Sapphire code |
| 90 | and vice-versa is finished and reliable. The hard bit, handling |
| 91 | Sapphire-style definition tables, isn't even begun yet. |
| 92 | |
| 93 | One of the projects I set myself over a year ago was a language, then |
| 94 | called `cdata', which would act as a pre-processor for C and allow |
| 95 | definition tables to be created without the amount of pain currently |
| 96 | required. (If you don't believe me, see the definition of the icon bar |
| 97 | menu in the `csapph' test program.) |
| 98 | |
| 99 | _____________________________________________________________________________ |
| 100 | |
| 101 | THE SAPPHIRE ENVIRONMENT |
| 102 | |
| 103 | |
| 104 | Sapphire doesn't use APCS. Let's get that clear right away. You try to |
| 105 | use APCS, and Sapphire will hate you for the rest of your life. |
| 106 | |
| 107 | Because the target language is assembler, Sapphire itself sometimes |
| 108 | plays fast and loose with the rules, but the basic ideas, such as they |
| 109 | are, are presented here. |
| 110 | |
| 111 | Registers R10-R15 are assigned special purposes. |
| 112 | |
| 113 | For architectural reasons, R15 is the program counter, and R14 is the |
| 114 | subroutine link register. Because R14 is modified by subroutine calls, |
| 115 | it is often used as a temorary register in computations. |
| 116 | |
| 117 | R13 points to a full descending stack. Sapphire doesn't allocate a very |
| 118 | big stack unless you ask it to, so be careful with recursion. There's |
| 119 | no stack limit checking either. |
| 120 | |
| 121 | R10 and R12 provide the program's current state. R12 is the `workspace' |
| 122 | pointer, and R10 is the `object' pointer. The idea is that R12 points |
| 123 | to a statically allocated data block, and R10 points to a `currently |
| 124 | interesting object'. For example, in a multi-document editor, R10 would |
| 125 | point to an information block describing the document currently being |
| 126 | processed. Because it's important to hide information between |
| 127 | components of a program, different components have different workspace |
| 128 | areas. Similarly, different parts of a program see the world at |
| 129 | different levels: the part of a drawing program which is concerned with |
| 130 | file-level operations keeps R10 as a pointer to the document anchor; the |
| 131 | part concerned with manipulating individual objects within a document |
| 132 | might keep R10 referring to an object. Note that R10 and R12 rarely |
| 133 | take part in inter-component interfaces. Object handles are passed |
| 134 | between components in low-numbered registers, as normal arguments, and |
| 135 | then copied into R10 later. |
| 136 | |
| 137 | R11 is Sapphire's `application context' pointer. It's also known as the |
| 138 | `scratchpad' pointer. From R11 upwards is the `scratchpad': a block of |
| 139 | memory available for any purpose. Any procedure may corrupt the |
| 140 | scratchpad, so it's never safe to assume that it is preserved over |
| 141 | inter-component calls. Immediately below R11 are workspace offsets. |
| 142 | These allow Sapphire and its client applications to find statically |
| 143 | allocated workspace. If R11 is pointing to the wrong place, Sapphire |
| 144 | will crash! |
| 145 | |
| 146 | On a procedure call, this is what usually happens: |
| 147 | |
| 148 | R0--R9 == arguments |
| 149 | R10, R12 == variables owned by caller |
| 150 | R13 == pointer to full descending stack |
| 151 | R11 == application context pointer (Scratchpad) |
| 152 | |
| 153 | On return: |
| 154 | |
| 155 | R0--R9 == return values, or usually preserved |
| 156 | R10, R12 preserved |
| 157 | R13 == preserved |
| 158 | R11 == application context pointer |
| 159 | Flags either preserved or set as a return value |
| 160 | |
| 161 | Sapphire's fond of returning information in the flags. For example, if |
| 162 | you ask for memory and there isn't any, allocation functions return with |
| 163 | carry set. Sometimes, errors are returned in the normal way, by setting |
| 164 | overflow and pointing R0 at an error block. If you don't return |
| 165 | something useful in a flag, preserve it. |
| 166 | |
| 167 | Sapphire behaves slightly different when it's calling its client back. |
| 168 | When you register a handler function, you supply a pointer to the code, |
| 169 | and R10 and R12 values to pass it. It will ensure that these values are |
| 170 | in those registers at call time. |
| 171 | |
| 172 | _____________________________________________________________________________ |
| 173 | |
| 174 | SAPPHIRE'S MEMORY MAP |
| 175 | |
| 176 | |
| 177 | Sapphire has strong ideas about how an application's memory is laid out. |
| 178 | Here's a poor attempt at a diagram: |
| 179 | |
| 180 | |
| 181 | _____________ <--- Top of current wimpslot |
| 182 | | | |
| 183 | : Flex : |
| 184 | : heap : |
| 185 | | | |
| 186 | |-------------| |
| 187 | | Resizing | |
| 188 | | heap | |
| 189 | |-------------| <--- Top of initial wimpslot |
| 190 | R13 | Stack | |
| 191 | |-------------| |
| 192 | R11 | Scratchpad | |
| 193 | | App context | |
| 194 | |-------------| |
| 195 | | | |
| 196 | |Default heap | |
| 197 | | | |
| 198 | |-------------| |
| 199 | R12 | Workspace | |
| 200 | |-------------| |
| 201 | | Application | |
| 202 | | code | |
| 203 | &8000 |_____________| <--- Base of application memory |
| 204 | |
| 205 | |
| 206 | Hmmm. Not too shabby. |
| 207 | |
| 208 | Now, in words: |
| 209 | |
| 210 | During initialisation, Sapphire's kernel finds the top of available |
| 211 | memory and puts the stack there, growing downwards. Below the stack, it |
| 212 | allocates the scratchpad area, so that there's a small amount of `slop' |
| 213 | between the the stack growing down and the scratchpad growing up. The |
| 214 | kernel then examines the available library components, and allocates |
| 215 | workspace for them, starting just above the application's read-write |
| 216 | area. (A Sapphire application shouldn't actually have any read-write |
| 217 | data -- R12-space is a much better way of doing the same job.) Once |
| 218 | that's done, it initialises an OS_Heap heap between the top of workspace |
| 219 | and the bottom of the application context area just below the |
| 220 | scratchpad. This is the `default heap'. |
| 221 | |
| 222 | Above the initial wimpslot is Flex space. Flex is Sapphire's shifting |
| 223 | heap manager. It's named after RISC_OSLib's shifting heap, although |
| 224 | it's completely rewritten, and much extended. Flex has complete control |
| 225 | over the area above the initial wimpslot. The first Flex block is taken |
| 226 | by a resizing heap manager, which creates a nonshifting heap there. This |
| 227 | is registered automatically with Sapphire's allocation manager, so you |
| 228 | can forget about it completely. |
| 229 | |
| 230 | This brings me on to... |
| 231 | |
| 232 | _____________________________________________________________________________ |
| 233 | |
| 234 | MEMORY MANAGEMENT |
| 235 | |
| 236 | |
| 237 | Sapphire sees three sorts of memory: |
| 238 | |
| 239 | * Static memory. This is allocated at initialisation time by the |
| 240 | kernel, and put into the workspace area. R12 points here. |
| 241 | |
| 242 | * Fixed-size blocks representing objects. These are allocated and |
| 243 | freed at run-time by the program, using Sapphire's allocation |
| 244 | manager. R10 points to one of these, usually. |
| 245 | |
| 246 | * Large or variable-size blocks representing data. These are |
| 247 | allocated and freed by the program using Flex. |
| 248 | |
| 249 | Typically each large block in Flex-space has an accompanying `anchor' |
| 250 | block in heap-space. |
| 251 | |
| 252 | The allocation manager attempts to paper over the mess involved with |
| 253 | having more than one nonshifting heap. STEEL actually does this job |
| 254 | better, putting the client in control of where blocks get allocated. |
| 255 | Sapphire's approach, to fill up one heap and move on to the next, is |
| 256 | simpler, but can lead to fragmentation. |
| 257 | |
| 258 | _____________________________________________________________________________ |
| 259 | |
| 260 | INITIALISATION |
| 261 | |
| 262 | |
| 263 | RISC_OSLib (and STEEL) programs start off with a huge chunk of |
| 264 | initialisation code, most of which is really boring: |
| 265 | |
| 266 | wimpt_init("progname"); |
| 267 | dbox_init(); |
| 268 | win_init(); |
| 269 | /* ... */ |
| 270 | |
| 271 | Sapphire gets rid of all that by using a neat feature of AOF. Each |
| 272 | library component, or `unit' as we called them, contains a four-word |
| 273 | table entry. The linker collects the entries together, and the kernel |
| 274 | can examine them all to see which units are linked in. Each entry |
| 275 | contains: |
| 276 | |
| 277 | * The address to store the unit's workspace offset. |
| 278 | * The size of workspace required. |
| 279 | * Minimum acceptable Scratchpad size (if more than 256 bytes). |
| 280 | * Address of initialisation procedure. |
| 281 | |
| 282 | By convention, the first word of a unit's workspace contains flags, bit |
| 283 | zero of which indicates that the unit has been initialised. A unit's |
| 284 | initialisation procedure performs the following actions: |
| 285 | |
| 286 | * It checks it's not already initialised. |
| 287 | * It initialises anything it depends on (this causes the required |
| 288 | units to be linked too). |
| 289 | * It initialises itself. |
| 290 | * It sets its initialised flag and returns. |
| 291 | |
| 292 | Library initialisation is a two-step process. The first step, |
| 293 | `sapphire_init', initialises the kernel and the basic memory map. The |
| 294 | second step, `sapphire_libInit', initialises the library units. |
| 295 | |
| 296 | _____________________________________________________________________________ |
| 297 | |
| 298 | DYNAMIC LINKING |
| 299 | |
| 300 | |
| 301 | The original idea was that Sapphire would be really small. Well, take a |
| 302 | look at its feature set and tell me that it's big. So we paid no |
| 303 | attention to making Sapphire dynamically linkable. When it became |
| 304 | obvious that this wasn't working properly, I started investigating |
| 305 | methods for making Sapphire into a DLL. |
| 306 | |
| 307 | I was saved by two features of Sapphire code: |
| 308 | |
| 309 | * Sapphire programs preserve R11 everywhere. |
| 310 | * Sapphire's kernel allocates workspace for the units at run-time. |
| 311 | |
| 312 | The first fact gives me re-entrancy automatically. R11 becomes the one |
| 313 | thing that the dynamically-linked Sapphire library can use to decide |
| 314 | where its workspace is. |
| 315 | |
| 316 | Finding workspace is rather grubby in Sapphire, so it's hidden beneath a |
| 317 | macro. For the curious, this is what happens: |
| 318 | |
| 319 | LDR R12,workoff_addr |
| 320 | LDR R14,[R11,#-magic_offset] |
| 321 | ADD R12,R14,R12 |
| 322 | |
| 323 | The trick works because the allocation of workspace /offsets/ depends |
| 324 | only on the order of units within the library, so they can be shared; |
| 325 | the value loaded from R11-space is worked out by the kernel and put in |
| 326 | application memory on startup. |
| 327 | |
| 328 | Most of the kernel ends up in the DLL. The application is linked |
| 329 | against a really small stub, which basically contains front ends for a |
| 330 | few kernel functions to (maybe) find the Sapphire DLLs, point to a |
| 331 | collection of tables, and call the corresponding function in the core. |
| 332 | |
| 333 | The exact mechanism is complicated. Interested readers are referred to |
| 334 | the source code. |
| 335 | |
| 336 | The upshot of all of this is that, although SDLS was designed |
| 337 | specifically to solve the problem of APCS shared libraries, Sapphire's |
| 338 | style of shared library requires much less code to be linked into the |
| 339 | client, much thinner veneering (because the kernel handles re-entrancy), |
| 340 | and the result is both more robust and cleaner. |
| 341 | |
| 342 | _____________________________________________________________________________ |
| 343 | |
| 344 | ERROR HANDLING |
| 345 | |
| 346 | |
| 347 | Sapphire programs know about two sorts of errors: |
| 348 | |
| 349 | * Program errors, which should never occur. |
| 350 | * Environmental errors, which must be anticipated. |
| 351 | |
| 352 | Environmental errors are reported by setting the V flag on exit from a |
| 353 | function. Sapphire routines are documented according to whether they |
| 354 | can return errors: it is safe to ignore the V flag on exit from a |
| 355 | routine whose documentation does not state that it returns errors. An |
| 356 | environmental error typically requires some sort of user intervention, |
| 357 | and suggests a friendly explanation that some sort of fault has |
| 358 | occurred. |
| 359 | |
| 360 | Program errors are raised by OS_GenerateError and caught by Sapphire's |
| 361 | error handler. There's a rule of thumb which states: `Never test for an |
| 362 | error you can't handle.' Sapphire follows this advice. Most calls to |
| 363 | the operating system have the X bit clear, causing errors to be sent to |
| 364 | the error handler. Program error messages are intentionally technical. |
| 365 | The idea is to encourage naive users to write them down, so that they |
| 366 | remember them and report them accurately. |
| 367 | |
| 368 | Part of the initial design was that a Sapphire application should |
| 369 | /never/ exit as a result of program fault. A user should always be able |
| 370 | to try to get to a save box and save data. (Too much experience of |
| 371 | Paint falling over with type 5s prompted this decision.) The |
| 372 | application pops up an error report giving the user a choice between |
| 373 | killing the program or trying to soldier on. Soldiering on seemed |
| 374 | remarkably successful in general. |
| 375 | |
| 376 | A later addition to Sapphire was `Structured Exception Handling', which |
| 377 | is a lower-level version of C++'s try/throw/catch exception handling. |
| 378 | This builds upon the existing base, and allows programs to tidy up in |
| 379 | the event of errors before propagating them back down to the bottom |
| 380 | level `Continue/Quit' handler. |
| 381 | |
| 382 | |
| 383 | This concludes the tour of Sapphire's run-time support functionality. |
| 384 | The rest describes plain ol' library features. |
| 385 | |
| 386 | _____________________________________________________________________________ |
| 387 | |
| 388 | EVENT HANDLING |
| 389 | |
| 390 | |
| 391 | Sapphire has a fairly simple but highly effective strategy for dealing |
| 392 | with events, inspired in part by the FilterManager module in RISC OS 3. |
| 393 | |
| 394 | The `event' unit is responsible for low-level event handling. |
| 395 | Applications call `event_poll' instead of calling the SWI Wimp_PollIdle |
| 396 | directly; the calling sequences are almost the same for both routines. |
| 397 | `event_poll' returns C set if it handled the event itself, and C clear |
| 398 | if it failed to handle it. A defalt handler procedure is provided to |
| 399 | perform any required actions for a particular event. A Sapphire |
| 400 | application's poll loop typically looks like this: |
| 401 | |
| 402 | loop BL event_poll |
| 403 | BLCC handle_unknowns |
| 404 | BLCC defHandler |
| 405 | B loop |
| 406 | |
| 407 | and that's it. |
| 408 | |
| 409 | Most of the hard work is done by filters. Sapphire knows about three |
| 410 | types of filters: |
| 411 | |
| 412 | * Pre-filters are shown arguments to Wimp_Poll before it's called, and |
| 413 | can modify them in controlled ways, e.g., clearing event mask bits, |
| 414 | or changing the `time-to-return' argument. A pre-filter can |
| 415 | `claim' a call to Wimp_Poll by storing a `fake' event in the buffer |
| 416 | and returning carry set. Other pre-filters are skipped, and the |
| 417 | fake event is passed to the post-filters. |
| 418 | |
| 419 | * Post-filters are shown the result from a call to Wimp_Poll. They |
| 420 | can perform any action they like, except for modifying the event. A |
| 421 | post-filter can claim an event by returning C set; other |
| 422 | post-filters are not called, and `event_poll' returns C set. |
| 423 | |
| 424 | * Fake-event filters are a novel idea introduced to handle transient |
| 425 | dialogue boxes. Immediately after a return from Wimp_Poll, Sapphire |
| 426 | scans the list of fake-event handlers. Each one is entitled to |
| 427 | `steal' this event and replace it with another by returning carry |
| 428 | set. Once an event has been stolen, it is passed through the |
| 429 | post-filters. When `event_poll' is called again, Sapphire |
| 430 | restores the previous genuine event and continues calling fake-event |
| 431 | filters from the point at which it left off. Thus, Sapphire |
| 432 | provides a general mechanism for inserting events. |
| 433 | |
| 434 | Everything else is based off this mechanism. |
| 435 | |
| 436 | The `win' unit dispatches window-specific events to window handlers by |
| 437 | registering a post-filter. The `menu' unit also establishes a |
| 438 | post-filter, and passes events to the owner of the current menu. |
| 439 | |
| 440 | The main use for fake-event handlers is to handle transient windows. A |
| 441 | separate Sapphire unit, `transWin', remembers the handle of the current |
| 442 | transient window. When an event arrives, its fake-event handler checks |
| 443 | whether the transient window is open. If it has been closed, it steals |
| 444 | the event, whatever it was, and substitutes a fake `window close' event |
| 445 | to be picked up by the owner of the transient window. |
| 446 | |
| 447 | [Actually, `transWin' is careful to allow redraw events through |
| 448 | unmolested. Bad window flicker results if redraw requests are tampered |
| 449 | with.] |
| 450 | |
| 451 | _____________________________________________________________________________ |
| 452 | |
| 453 | MENUS IN SAPPHIRE |
| 454 | |
| 455 | |
| 456 | Sapphire knows about two types of menus, and its interface to |
| 457 | applications reflects this. Standard RISC OS transient menus will be |
| 458 | familiar to most readers. Straylight tearoff menus will be less well |
| 459 | known -- see Glass for a real-life example of tearoff menus in action. |
| 460 | |
| 461 | Sapphire's interface to handling menus is inspired by Computer Concepts' |
| 462 | Advanced Box Interface system. Menus are always constructed |
| 463 | dynamically, as needed. The procedure `menu_create' is given four |
| 464 | arguments: |
| 465 | |
| 466 | * The address of a menu definition. |
| 467 | * The address of a menu event handler. |
| 468 | * R10 and R12 values to pass to the handler. |
| 469 | |
| 470 | The `menu_create' procedure may be called any number of times before the |
| 471 | next Wimp_Poll: the menu definitions are concatenated. This allows |
| 472 | applications to create dynamic and context-sensitive menus easily. |
| 473 | |
| 474 | The first `menu_create' call is different from the others: the menu |
| 475 | definition must contain a menu title. (It need not contain any menu |
| 476 | items, though.) Both the menu title and the items are /variable size/ |
| 477 | objects -- the more odd features a menu item has, the more space its |
| 478 | definition occupies. Menu definitions are also /read-only/. Dynamic |
| 479 | features like ticking and shading must be handled separately. |
| 480 | |
| 481 | Each menu item (and title) is begun with a `feature mask' -- a word |
| 482 | containing a flag bit for each feature. The feature mask is followed by |
| 483 | data appropriate to the various features selected. For example, the |
| 484 | `has a submenu' feature requires a pointer to the submenu definition and |
| 485 | a pointer to the procedure which will handle events for the submenu. |
| 486 | |
| 487 | Menu features which require dynamic information (e.g., context-sensitive |
| 488 | message strings, or ticking and shading) have, as part of their feature |
| 489 | data, offsets from the menu handler's R10 containing the required |
| 490 | information. The `shade item' feature requires an offset from R10 and a |
| 491 | bit position -- it will shade the item when the appropriate bit in the |
| 492 | word at [R10, #offset] is set. Sapphire's menu system understands the |
| 493 | concept of `radio items' -- groups of items of which only one may be |
| 494 | ticked at a time. The feature data for a radio item consists of an R10 |
| 495 | offset and a value to match -- the application then sets [R10, #offset] |
| 496 | to be (for example) 0 for the first item in the group, 1 for the second, |
| 497 | and so on. |
| 498 | |
| 499 | It's important to get the hang of the concepts here, because lots of |
| 500 | other bits of Sapphire use this same idea. |
| 501 | |
| 502 | The `menuDefs.sh' header file contains a large number of macros which |
| 503 | hide most of the mess of this. |
| 504 | |
| 505 | _____________________________________________________________________________ |
| 506 | |
| 507 | DIALOGUE BOXES |
| 508 | |
| 509 | |
| 510 | Sapphire's dialogue box handling is extremely powerful. There's also a |
| 511 | lot of code in `dbox.s' which does jobs that the WindowManager ought to |
| 512 | do, or does wrong. |
| 513 | |
| 514 | Dialogue boxes are `resources'. Sapphire tries to keep the |
| 515 | interface to handling resources consistent. When you want one, you |
| 516 | `create' it, and when you don't need it any more, you `destroy' it. |
| 517 | A successful `create' of a dialogue box yields a `dbox' -- a handle |
| 518 | which represents the dialogue box. |
| 519 | |
| 520 | Sapphire is carefully constructed so that dialogue boxes don't need to |
| 521 | exist unless they're actually in use. Most programs create dialogue |
| 522 | boxes only when they need to appear on-screen, and destroy them again |
| 523 | once they're closed. To this end, Sapphire generates fake close events |
| 524 | for transient windows. |
| 525 | |
| 526 | Dialogue boxes can be created from several different sources of |
| 527 | information, and to handle this, there are actually three different |
| 528 | `create' routines: |
| 529 | |
| 530 | * `dbox_create' is the standard call. A client passes a string naming |
| 531 | the template from which to create the dialogue, and the dialogue box |
| 532 | manager returns a handle. The window definition is copied, along |
| 533 | with all the indirected data, so that you can create multiple |
| 534 | instances of a template without tripping over aliasing problems. |
| 535 | |
| 536 | * `dbox_fromEmbedded' creates a dialogue from an `embedded template' |
| 537 | -- a compact and easily expanded representation of a window template |
| 538 | which is statically linked into the client program. Again, the data |
| 539 | is copied from the template, so aliasing problems don't exist. |
| 540 | |
| 541 | * `dbox_fromDefn' is the most powerful call (and the others are |
| 542 | implemented in terms of it). The client passes a pointer to a |
| 543 | complete window definition, which the Sapphire uses `as is', |
| 544 | without copying it. |
| 545 | |
| 546 | Embedded templates were introduced for very small programs (e.g., |
| 547 | DLLMerge). Sapphire makes use of them because common dialogue boxes |
| 548 | have been stored in a shared `Sapphire.Resources' DLL as embedded |
| 549 | templates. This makes programs easier to upgrade for new versions of |
| 550 | Sapphire (just copy the new DLLs in and you get the new dialogues free) |
| 551 | and reduces resource requirements since there's only one copy in memory |
| 552 | at any given time. |
| 553 | |
| 554 | Displaying dialogue boxes introduces more flexibility. The procedure |
| 555 | `dbox_open' is given a dbox and an `open style', and a collection of |
| 556 | arguments appropriate to the style. Styles permitted are: |
| 557 | |
| 558 | * In its current position (or in the position it was in the template |
| 559 | file). |
| 560 | |
| 561 | * In the middle of the screen. |
| 562 | |
| 563 | * Centred over the mouse pointer. |
| 564 | |
| 565 | * At a given Y coordinate (but the current X coordinate). |
| 566 | |
| 567 | * At a given X and Y position. |
| 568 | |
| 569 | The open style also contains a `transient or persistent' flag, which is |
| 570 | useful for dialogue boxes hanging off of menu items. If a menu is open |
| 571 | currently, Sapphire will automatically open the dbox as a submenu, in |
| 572 | the correct place, unless you explicitly instruct it not to do this |
| 573 | using another open style bit. |
| 574 | |
| 575 | Sapphire doesn't pass Wimp events directly on to client event handlers. |
| 576 | Instead, it pre-processes them, putting the important information from |
| 577 | the event into registers, and sending a `dbox event' to the handler. |
| 578 | The handler can then either `claim' the event for itself (by saying that |
| 579 | it's handled it) or pass it on. Unclaimed events are acted upon by |
| 580 | Sapphire. |
| 581 | |
| 582 | As an example of the sort of preprocessing Sapphire does for dialogue |
| 583 | box events, it breaks a redraw request into an individual event for each |
| 584 | rectangle. If its redraw events are unclaimed (as they ought to be), it |
| 585 | will call Sculptrix to fill in the 3D borders around the icons. |
| 586 | |
| 587 | Sapphire handles radio buttons itself, if click events on them are |
| 588 | unclaimed. Using type-11 buttons and a non-zero ESG means that the user |
| 589 | can deselect all buttons in a set by clicking the selected one with |
| 590 | `adjust'. An application can deal with this by selecting the icon |
| 591 | explicitly, but (a) this causes flicker, and (b) if the application is |
| 592 | going to the trouble of having explicit code to deal with the situation, |
| 593 | it may as well do the job properly. Sapphire considers an icon with |
| 594 | button type 3 and non-zero ESG to be a `Sapphire radio button' and does |
| 595 | the right thing with it. |
| 596 | |
| 597 | Shaded icons are also handled by Sapphire rather than the Wimp, because |
| 598 | the standard behaviour is highly inconsistent. For example, sometimes |
| 599 | icon backgrounds are shaded in addition to foregrounds. In text-and- |
| 600 | sprite icons, the text is /not/ shaded when it ought to be (for |
| 601 | consistency with text-only icons). It's basically a mess. Sapphire |
| 602 | uses a complicated shading algorithm which seems fairly close to the |
| 603 | behaviour of ABI. |
| 604 | |
| 605 | Sapphire also handles caret blinking and cursor changing over writable |
| 606 | icons automatically. |
| 607 | |
| 608 | Keypresses also receive default handling from Sapphire. Unclaimed |
| 609 | cursor keys cause the caret to move between writable icons, scrolling |
| 610 | the window where necessary (both horizontally and vertically) to ensure |
| 611 | that the new focus icon is visible. |
| 612 | |
| 613 | The procedure to set a string in an indirected icon is probably |
| 614 | excessive. It ensures that the string really needs changing before |
| 615 | flickering the icon. It truncates the string rather than overflowing |
| 616 | the icon's buffer, discarding either the start or the end depending on |
| 617 | the icon's `right align' flag, optionally putting a `...' to indicate |
| 618 | that a truncation was performed. |
| 619 | |
| 620 | Sapphire inherited a curious feature called `embedded titles' from |
| 621 | STEEL, which in turn was inspired by RISC OS version of the game |
| 622 | `Elite'. A client nominates an icon within a window which has no title |
| 623 | bar as being an `embedded title'. Sapphire will thereafter |
| 624 | automatically draw a Sculptrix group box around this icon, using the |
| 625 | window's real title text as the group title. This is used in `Info' |
| 626 | windows, as well as the warning, note and error windows. |
| 627 | |
| 628 | Sapphire can handle `nonpolling' windows. In some cases it's desirable |
| 629 | to arrest the user's attention by requiring acknowledgement of a message |
| 630 | or the taking of a decision, and sometimes continuing to poll would be |
| 631 | dangerous or undesirable for other reasons. For example, a serious |
| 632 | error report mustn't multitask, because the error might recur. The `To |
| 633 | save...' message caused by attempting to save a file without a full |
| 634 | pathname shouldn't multitask, because the click on the OK button will |
| 635 | close the menu tree (which is extremely irritating). The only way to |
| 636 | handle a prequit message under RISC OS 2 is to ask the user whether |
| 637 | quitting is permitted in a nonpolling way, so the decision to cancel a |
| 638 | shutdown sequence can be made before an acknowledgement is returned to |
| 639 | the task manager. The handling for nonpolling windows is split into two |
| 640 | parts. The `nopoll' unit displays dialogue boxes and reports mouse |
| 641 | clicks and keypresses as fake events through Sapphire's pre-handler |
| 642 | mechanism (so nonpolling dialogues present an identical programmer |
| 643 | interface to normal ones). The `buttons' unit displays a given |
| 644 | dialogue, permitting a collection of action buttons to be given |
| 645 | appropriate pieces of text. |
| 646 | |
| 647 | The real richness of Sapphire's dialogue box handling comes from custom |
| 648 | controls. A whole subsystem, `dbx' is provided for dealing with custom |
| 649 | controls. |
| 650 | |
| 651 | The client registers a table defining the required controls for a |
| 652 | particular dialogue box. The table contains variable size entries, each |
| 653 | one beginning with a standard header: |
| 654 | |
| 655 | * Number of the icon which hosts the control. |
| 656 | * Pointer to the control definition. |
| 657 | * Various flags (control specific). |
| 658 | * The offset of the control's writable data within the dialogue |
| 659 | handler's R10-space. |
| 660 | |
| 661 | This is followed by whatever constant data the control handler requires. |
| 662 | Sapphire dbx controls are always hosted by icons. This makes them easy |
| 663 | to lay out in template editors. |
| 664 | |
| 665 | Custom controls generate their own events which are sent to the client's |
| 666 | handler procedure. They are also given dialogue box events (after the |
| 667 | client has had a chance to claim them for itself) as `dbx events'. For |
| 668 | example, Sapphire will only ask a control to draw itself if (a) the |
| 669 | control requests redraw events and (b) the current rectangle intersects |
| 670 | the control's host icon. |
| 671 | |
| 672 | Sapphire takes redrawing of controls very seriously. It ensures that |
| 673 | the graphics clipping area is set to the intersection of the control's |
| 674 | host icon and the redraw rectangle, to prevent any `accidents' from |
| 675 | messing up the rest of the display. It passes this amended rectangle to |
| 676 | the control to allow it to perform source-level clipping. The custom |
| 677 | RGB-coloursquare and HSV-colourcircle controls in the (unfinished) |
| 678 | colour selector use the provided rectangle to plot the new graphics by |
| 679 | direct screen access. |
| 680 | |
| 681 | Predefined custom controls are: |
| 682 | |
| 683 | * A slider. This is written very carefully to avoid any flicker while |
| 684 | the bar is being dragged or updated. |
| 685 | |
| 686 | * A `bump' arrow button, which presses its icon in while it's being |
| 687 | held down, and autorepeats. The arrow control keeps a count of how |
| 688 | many `ticks' have been missed, and sends them to the dialogue box as |
| 689 | large packets, rather than individually. This makes programs which |
| 690 | spend a long time processing bump requests feel responsive even when |
| 691 | they're really being slow. |
| 692 | |
| 693 | * A file icon, which represents a filetype in a window. File icons |
| 694 | can optionally be draggable (for save operations). When solid drags |
| 695 | are requested by the user (and the DragASprite module is available), |
| 696 | the control will make the icon disappear while the icon is being |
| 697 | dragged, to produce the illusion of the icon being `lifted off' the |
| 698 | dialogue box. |
| 699 | |
| 700 | * A `string set' control, which drops down a menu of strings and |
| 701 | allows the user to choose one, which it displays. |
| 702 | |
| 703 | * A simple `colour pot' which allows a user to choose one of the |
| 704 | sixteen standard Wimp colours. This control pops open a small |
| 705 | dialogue box of its own when requsted. |
| 706 | |
| 707 | Custom controls provide a great deal of functionality at very little |
| 708 | programmer effort. Why no other library for RISC OS handles them |
| 709 | properly is a mystery to me. The Toolbox has a go, but fails miserably; |
| 710 | compared to the power of Sapphire's `dbx' subsystem, it might as well |
| 711 | not bother. |
| 712 | |
| 713 | (Sapphire was mature when the Toolbox betas were available. The idea |
| 714 | for proper custom controls came from OS/2, but the implementation is |
| 715 | very much in Sapphire's own style.) |
| 716 | |
| 717 | _____________________________________________________________________________ |
| 718 | |
| 719 | DATA TRANSFER |
| 720 | |
| 721 | |
| 722 | It used to be the case that data transfer was one of the really hard |
| 723 | bits of an application. I remember trying to put it off as long as |
| 724 | possible whenever I wrote a STEEL application. Sapphire tries very hard |
| 725 | to make data transfer as painless as possible. |
| 726 | |
| 727 | Both loading and saving have two layers to them. Underneath, there's a |
| 728 | low-level interface which exposes the nature of the transfer (whether |
| 729 | it's to a file, or in-memory). Above that, there's an abstract |
| 730 | interface which presents both transfer types as a (non-seekable) stream |
| 731 | of data, providing buffering and a simple programming interface. This |
| 732 | high-level interface works perfectly well on files even without a data |
| 733 | transfer context, and occasionally gets (ab)used as a general stream |
| 734 | interface to file data, for example, when saving preferences files. |
| 735 | |
| 736 | The lower-level code was designed to be useful both in conjunction with |
| 737 | the high-level interfaces and on its own. Often, the low-level |
| 738 | interface is the better choice. |
| 739 | |
| 740 | Oddly, the low-level interface is higher-level than RISC_OSLib's. |
| 741 | |
| 742 | A low-level data save operation is initiated by the `save' call. It |
| 743 | requires a large number of arguments, detailing the recipient's window |
| 744 | handle, the filename, filetype, and a block of handling routines. The |
| 745 | handling routines are: |
| 746 | |
| 747 | * Save: write your data to a file, given its name. Return an error if |
| 748 | saving failed. |
| 749 | |
| 750 | * Send: return (giving a base pointer and size) a block to send to the |
| 751 | recipient. Return CS if this is the last block, or CC if there's |
| 752 | more to come. The first time this handler is called, it's given the |
| 753 | value 0 in R2; after that, the return value of R2 is passed back in |
| 754 | the next call, to provide some sort of context. It's called over |
| 755 | and over until either the recipient complains or the handler returns |
| 756 | end-of-data. |
| 757 | |
| 758 | * Success: the data was transferred successfully (and may or may not |
| 759 | be permanently safe). |
| 760 | |
| 761 | * Failed: the data transfer failed, either because of a local error, |
| 762 | or because the receiver failed. |
| 763 | |
| 764 | Note that the `send' handler can return a block of any size. It's |
| 765 | Sapphire's responsibility to handle the recipient's buffer size. In the |
| 766 | case where all the data is in one big flex block, the send routine |
| 767 | becomes trivial. Sapphire makes RAM transfer /easier/ than writing to a |
| 768 | file! |
| 769 | |
| 770 | Loading data in similar in spirit. There's a single routine, `load', |
| 771 | which is given a block of handlers: |
| 772 | |
| 773 | * InitBuf: create a buffer in which to receive data, and perform any |
| 774 | other setting up necessary to start a RAM transfer. The handler is |
| 775 | given an estimated file size, and the proposed leafname. |
| 776 | |
| 777 | * KillBuf: RAM transfer has failed, so destroy the buffer (if it was |
| 778 | only temporary). This is called when RAM transfer was unsuccessful |
| 779 | for some reason. |
| 780 | |
| 781 | * Extend: the current buffer was filled: I need a new one. (It's |
| 782 | called `extend' because the normal reaction is to extend a flex |
| 783 | block and return the new area.) |
| 784 | |
| 785 | * DoneBuf: RAM transfer has succeeded, so do whatever's necessary. |
| 786 | (Typically, an application might free some `slop space' at the end |
| 787 | of the buffer.) |
| 788 | |
| 789 | * File: load the data from a given file. |
| 790 | |
| 791 | * Done: data transfer succeeded. |
| 792 | |
| 793 | * Failed: data transfer failed, either because of something we did |
| 794 | wrong or because the sender had problems. |
| 795 | |
| 796 | Sapphire provides routines to do most of the work of the InitBuf, |
| 797 | KillBuf, Extend, DoneBuf and File handlers in the case where we're just |
| 798 | loading directly into a flex block, and in many cases, the |
| 799 | supplied KillBuf, Extend and DoneBuf routines can be used directly. |
| 800 | |
| 801 | The higher-level interfaces `xsave' and `xload' are designed to link |
| 802 | onto the low-level interfaces, and provide handlers which can (in many |
| 803 | cases) be given directly to the low-level calls. They take as an |
| 804 | argument a tranfer routine, which is called to do the actual save or |
| 805 | load operation. It is called just like a normal subroutine. When it |
| 806 | returns, the transfer is ended. It calls `xsave' or `xload' routines |
| 807 | to read and write bytes, words, strings or whole blocks of memory; |
| 808 | Sapphire buffers data where this would be useful. Sapphire provides a |
| 809 | separate stack for the transfer routine, so that the read or write |
| 810 | routines can pause for a Wimp_Poll to send or receive more data by RAM |
| 811 | transfer, although this is invisible to the transfer routine, which just |
| 812 | sees a simple interface to a stream of data. Errors from the transfer |
| 813 | are carefully propagated back to the transfer routine, which sees error |
| 814 | returns from read or write operations. |
| 815 | |
| 816 | Sapphire includes a standard `saveAs' box, whose interface is slightly |
| 817 | simpler than the `save' routine. |
| 818 | |
| 819 | _____________________________________________________________________________ |
| 820 | |
| 821 | THE SAPPHIRE CHUNK FORMAT, AND PREFERENCES HANDLING |
| 822 | |
| 823 | |
| 824 | A Sapphire `chunk' file is very simply a file representation of a list |
| 825 | of named chunks of data. Sapphire provides functions to read chunk |
| 826 | files, claim chunks, and write chunk files back again. |
| 827 | |
| 828 | When a chunk file is loaded, each chunk is placed in a Flex block. A |
| 829 | client can `claim' a chunk. Once this is done, the client can either |
| 830 | modify the data in-place, or free the block and provide a pointer to a |
| 831 | procedure to write equivalent data to the chunk file when it's saved. |
| 832 | |
| 833 | The original idea was to support preferences files in a modular way -- |
| 834 | each component could find its own preferences chunk without needing to |
| 835 | know about anything other than the chunk system. We noticed that this |
| 836 | could easily be extended to handle binary data, and we could base a |
| 837 | general file format on the idea. A typical Straylight document would |
| 838 | look something like this: |
| 839 | |
| 840 | ; |
| 841 | ; Generated by Straylight Frobnitz |
| 842 | ; |
| 843 | |
| 844 | [Header] |
| 845 | Format = Frobnitz data |
| 846 | FormatVersion = 3.15 |
| 847 | Author = J. R. Hacker |
| 848 | |
| 849 | [FrobData] |
| 850 | Bin[00]....[binary rubbish] |
| 851 | |
| 852 | [FrobOptions] |
| 853 | AutoDelay = 300 |
| 854 | AutoMods = 50 |
| 855 | ... |
| 856 | |
| 857 | You get the idea. The text chunks can be modified by anyone with a text |
| 858 | editor -- the binary areas are self-contained and don't care where they |
| 859 | are in a file. |
| 860 | |
| 861 | Some user preferences are best stored as raw chunks. For example, the |
| 862 | `FontDir' program keeps a chunk containing the list of font directories |
| 863 | known to it. Most are better expressed through the sort of key/value |
| 864 | syntax shown in the above example. Sapphire provides a separate |
| 865 | mechanism for handling these which interwork with the chunk file system. |
| 866 | |
| 867 | The `options' unit will, when requested, claim a chunk from a chunk |
| 868 | file, and translate its textual contents into an easily digestable |
| 869 | binary format. When the chunk file is saved, Sapphire will |
| 870 | automatically read the binary data, and translate whatever's there back |
| 871 | into text. Using text as an external representation insulates the |
| 872 | client program from version changes -- default values can be provided |
| 873 | for keys which aren't defined, for example. |
| 874 | |
| 875 | Each key/value pair in the chunk has the general format: |
| 876 | |
| 877 | <key> [=] <string> [;<comment>] |
| 878 | |
| 879 | A <string> is either a sequence of unquoted characters, or quoted by one |
| 880 | of the pairs `', '', or "". A quote character can be duplicated to |
| 881 | insert a literal quote in the string. Any of the characters `#', `|' or |
| 882 | `;' begins a comment (although `;' is preferred because we're assembler |
| 883 | programmers). |
| 884 | |
| 885 | The set of permissable options is provided to Sapphire as a table, each |
| 886 | entry of which states the name of the key, the offset within the options |
| 887 | block to store the (binary) value, the type of the value, and any data |
| 888 | the type handler needs for its own purposes. It's another |
| 889 | variable-size-entry table, like a menu definition or a dbx control list. |
| 890 | More types can easily be written by client programs. Standard handlers |
| 891 | are provided: |
| 892 | |
| 893 | * The string type reads quoted or unquoted strings. It always writes |
| 894 | quoted strings. |
| 895 | |
| 896 | * The integer type reads signed or unsigned integers in any base (up |
| 897 | to 36). It usually writes decimal numbers although it can be |
| 898 | persuaded to use a different base. |
| 899 | |
| 900 | * The literal type never reads anything. It writes out a string |
| 901 | (maybe some kind of comment) when requested. |
| 902 | |
| 903 | * The enum type reads a string, looks it up in a table, and stores the |
| 904 | index. The match is case-insensitive. Abbreviations are |
| 905 | accepted; ambiguous abbreviations match the first string found in |
| 906 | the table. It writes out the full correct-case string. |
| 907 | |
| 908 | * The bool type is a special case of the enum type -- it reads either |
| 909 | one of `true', `on' or 'yes' for a true value, or `false', `off' or |
| 910 | `no' for false, and sets a single bit for its answer (for space |
| 911 | efficiency). On output, it writes one of `true' or `false', unless |
| 912 | requested to use `on' or `off', in which case it also suppresses the |
| 913 | optional `=' sign on output. |
| 914 | |
| 915 | * The version type reads version numbers of the form `xxx.yz' and |
| 916 | stores them as integers, multiplied by 100. It writes them out |
| 917 | in the same form, with trailing zeros if necessary. |
| 918 | |
| 919 | The preferences system DoggySoft added into WimpExtension is the nearest |
| 920 | thing which comes close, but Sapphire does the job in a much more |
| 921 | powerful way. This is actually quite surprising, since the unit isn't |
| 922 | particularly large. (This is partly due to an interesting coding trick |
| 923 | I came up with to reduce typing, which I've entirely failed to make use |
| 924 | of in any high-level language. See `choices/options.s' for details.) |
| 925 | |
| 926 | _____________________________________________________________________________ |
| 927 | |
| 928 | HIGH LEVEL LANGUAGE INTERFACE |
| 929 | |
| 930 | |
| 931 | This is stil very sketchy, but a discussion may interest some readers. |
| 932 | The C interface has to deal with several problems: |
| 933 | |
| 934 | * Sapphire's procedure calls aren't even a little bit compatible with |
| 935 | APCS. Sapphire gains a lot of its power and elegance from its lax |
| 936 | calling conventions. |
| 937 | |
| 938 | * C programmers tend to use different names for commonly used |
| 939 | functions. |
| 940 | |
| 941 | * Sapphire's variable-size-entry tables aren't easy to represent in C. |
| 942 | |
| 943 | The last problem hasn't been addressed. The trivial `Hello, world' |
| 944 | example program contains a horrible block of hex where the menu |
| 945 | definition is meant to be. To be fair, we envisaged the user-interface |
| 946 | parts of Sapphire applications to be in assembler, to keep the |
| 947 | user-facing parts of a program light and responsive, while the back-end |
| 948 | bits were in C. |
| 949 | |
| 950 | The second problem just involves writing a lot of veneers and playing |
| 951 | with macros. I didn't bother trying to make the functions ANSI |
| 952 | compliant. Only the bare bones of a standard library is provided. |
| 953 | |
| 954 | The first is the only technically interesting problem. Making C call |
| 955 | Sapphire is a fairly simple task of inserting a veneer in the right |
| 956 | place. I wrote a `_call' function which works rather like `_swi', to |
| 957 | allow C to call any Sapphire function. There's a small problem to do |
| 958 | with R11 which needs dealing with, though. My solution was simple -- |
| 959 | since Sapphire doesn't have any stack checking anyway, compile code with |
| 960 | stack limit checking turned off and move R11 into R10 (APCS's stack |
| 961 | limit pointer) and back to R11 on inter-language borders. |
| 962 | |
| 963 | The really interesting problem, then, is making Sapphire understand how |
| 964 | to call C functions. Again, some hacking is performed. Any C function |
| 965 | which is intended to be called by Sapphire is declared as follows: |
| 966 | |
| 967 | __sapph(funcname)(regset *r [, type *r10 [, type *r12 [, char *scratch]]]) |
| 968 | |
| 969 | (APCS's way of passing arguments in registers allows the flexibility of |
| 970 | arguments -- if a function doesn't need R10 or R12 it doesn't need to |
| 971 | declare them. More arguments can be read by putting them after |
| 972 | `scratch' -- they just get pulled off the stack.) |
| 973 | |
| 974 | The macro constructs a function with the given name, just before the |
| 975 | actual compiled function, whose name is changed to `__sapph__funcname'. |
| 976 | |
| 977 | The actual code generated by the compiler for this atrocity is: |
| 978 | |
| 979 | funcname STMFD R13!,{R14} |
| 980 | MOV R14,PC |
| 981 | B __sapph_veneer |
| 982 | |
| 983 | __sapph__funcname |
| 984 | <APCS entry sequence> |
| 985 | ... |
| 986 | |
| 987 | The `__sapph_veneer' routine knows that the function it's meant to call |
| 988 | is pointed to by R14. It stores all the registers on the stack, points |
| 989 | R0 at them (to make the regset variable), sets up the other arguments, |
| 990 | puts R11 into R10, and calls the C routine. On exit, it pulls the |
| 991 | registers off the stack, puts R11 back in its proper place, sets |
| 992 | flags appropriately, and returns. |
| 993 | |
| 994 | The C function can set return values by modifying the `regset' argument |
| 995 | it's passed. It can also modify the flags by returning an appropriate |
| 996 | value as its result. For example |
| 997 | |
| 998 | return (C_set | V_clear); |
| 999 | |
| 1000 | returns with C set, V clear and the other flags unaltered. |
| 1001 | |
| 1002 | This is a very hamfisted way of dealing with the problem. But Sapphire |
| 1003 | was never designed to work with C. In fact, it's almost true to say |
| 1004 | that Sapphire was designed never to work with C -- the fact that it's |
| 1005 | possible after all is more a credit to humen ingenuity than Sapphire's |
| 1006 | design. |
| 1007 | |
| 1008 | _____________________________________________________________________________ |
| 1009 | |
| 1010 | CONCLUSION |
| 1011 | |
| 1012 | |
| 1013 | Your tour through the interesting bits of Sapphire is complete. There |
| 1014 | are plenty of other `neat toys' in there, waiting for you to find them. |
| 1015 | Most source files contain some sort of interesting twist on an old idea, |
| 1016 | or a neat programming trick. |
| 1017 | |
| 1018 | The sources are commented to excess. Almost every line contains a |
| 1019 | comment. Obscure tricks and other bits of non-obvious code are usually |
| 1020 | tagged `Horrid hack' or `Neat trick', at least the first time we used |
| 1021 | that particular construction. Searching for `hack' should produce lots |
| 1022 | of interesting code to read. |
| 1023 | |
| 1024 | Anything else you want to know: just read the header file documentation |
| 1025 | and/or the source code. Or you can ask me. Just bear in mind that it's |
| 1026 | been a long time since I wrote anything which used Sapphire and I'll |
| 1027 | probably have to read the headers and/or the source code anyway. |
| 1028 | |
| 1029 | Sapphire, for Tim and myself, has been a labour of love. I'm extremely |
| 1030 | proud of it: of its small size, its speed, its elegant design, its power |
| 1031 | and versatility. Please, treat it with respect. |
| 1032 | |
| 1033 | I'm almost in tears as I type this, so I'd better stop. Thanks for |
| 1034 | reading, |
| 1035 | |
| 1036 | |
| 1037 | |
| 1038 | Mark Wooding, mdw@excessus.demon.co.uk |
| 1039 | 17 December 1997 |