Book Contents Previous Chapter Next Chapter
Magic Cap provides an environment for supporting persistent objects. An object is a structure that includes data elements and a set of actions that operate on the data. Each object provides interfaces to its actions via operations and to its data elements via attributes. Internally, objects specify functions that implement operations and storage locations that implement attributes. These internal functions are called methods and the storage locations are called fields.
Objects are described by templates called classes. Each class is defined in terms of its difference from another class, called its superclass. Each class inherits operations and attributes from its superclasses, and the new class can redefine any operation to enhance or change its behavior. All classes that define objects ultimately descend from class Object.
When you call an operation, the method that executes in response to the call is defined by that object's class or by one of its superclasses. When an operation is called, Magic Cap selects the method to run by examining the object's class and, if necessary, its superclasses. The object that has its operation called is the responder.
Personal communicators based on Magic Cap are designed to keep permanent user data primarily in RAM rather than on an external storage device such as a hard disk. This RAM is called persistent memory because it retains its contents whether main power is turned on or off, as long as some power source is applied (main battery, backup battery, or AC power). Persistent memory is useful for permanent user data, such as electronic mail messages, name cards, appointments, and so on.
To help ensure that this persistent data isn't damaged, it is protected from change through a set of internal routines provided by Magic Cap. Packages can't write directly to persistent memory. Instead, packages write changes to a buffer area of memory. Magic Cap updates persistent memory from this buffer area periodically, ensuring that packages don't corrupt structures in persistent memory. Because persistent memory is protected from direct change, writing to it is somewhat slower than if the memory were not protected.
As a performance enhancement, Magic Cap defines another kind of RAM that is not protected from change and does not retain its contents when power shuts off. This kind of memory, called transient memory, is useful for objects that are created and destroyed within the scope of an executing function, objects that can be recreated from persistent data, and other temporary objects. When you create new objects, you can specify whether they should exist in persistent or transient memory. If you create an object in transient memory, your package must be prepared to recreate it if main power shuts off.
All Magic Cap objects in memory, whether in RAM, ROM, or on a PCMCIA card (which Magic Cap calls PC cards), are collected into groups called clusters. Magic Cap maps the memory on PC cards into memory directly. The memory on these cards is not treated as external storage. A cluster is associated with a contiguous area in memory and manages all the objects in that area. Each object belongs to exactly one cluster. Like most structures in the system, clusters are themselves objects. Each cluster in RAM controls only one kind of memory, either persistent or transient.
Magic Cap includes two clusters created by the system, called the system persistent cluster and the system transient cluster. Every package can directly address objects in the system clusters. In addition to these system clusters, when you create a new package, your package includes its own persistent cluster and transient cluster. Packages can address objects in clusters belonging to other packages through an import operation. Some Magic Cap implementations have no transient RAM. On those implementations, all clusters are persistent.
Every package defines a set of clusters in which it can address objects directly. This set of clusters is a context. For more information on clusters and contexts, see this chapter's Clusters and Contexts section.
Every object in Magic Cap can be addressed at runtime with a 32-bit value called an object ID. You use an object's ID when you execute one of its operations, get access to its fields, or pass it as a parameter. When you use an object ID, Magic Cap's object runtime quickly and efficiently converts the object ID into a pointer to the object.
When you define objects at build time, you don't use object IDs. Instead, you use values that are automatically converted to object IDs at runtime. See this chapter's Creating Objects at Build Time section for more information.
Your package can use an object ID to address any object created by the package itself or any object in a system cluster. Magic Cap defines several kinds of object IDs that can be used for various special purposes. For more information on object IDs, see this chapter's Addressing Objects section.
You will typically see object IDs as hexadecimal numbers displayed in the Inspector and in debuggers.
This section discusses creating objects, both at build time and at runtime, and destroying objects in a running Magic Cap package.
Magic Cap provides two fundamentally different ways to create objects: you can specify them in an instance definition file, or you can create them programatically at runtime. Objects specified in an instance definition file are created when the package is built and are loaded along with the package. You can create objects programmatically at runtime by calling various operations defined by Magic Cap.
Objects created at build time by defining them in an instance definition file are said to be created statically. Objects created programatically at runtime are said to be created dynamically.
To create an object statically, you define the object at build time using the name of its class and an instance definition ID, along with values for its fields, in an instance definition file (an Objects.Def file). This is an example of an object specified in an instance definition file:
Instance LyricText 1144; textValue: 'Everything right is wrong again.'; End Instance;
This example specifies a new object of class LyricText that will be created in the package. The first line of the instance definition specifies the class of the object, followed by the object's instance definition ID. The instance definition ID is an unsigned 32-bit integer that uniquely identifies the instance within the instance definition file. Instances have definition IDs because instance definitions often refer to other definitions in the file. For example, the definition of a list object contains the instance definition IDs of other objects, as follows:
Instance SongSet 339; firstSong: (Song 589); middleSong: (Song 2281); finale: (Song 77); End Instance;
Note that instance definition IDs are not the same as object IDs, but are used in parallel. Object IDs are used only at runtime, while instance definition IDs are used only at build time. When you build your package, ObjectMaker assigns an object ID to each object. Objects created at build time are installed in the package's persistent cluster.
Instance definitions must include values for every field of the objects they specify. Because the definition above for an object of class LyricText includes a value for one field, textValue, we can assume that class LyricText defines only that one field for its objects. Similarly, the definition for class SongSet should show that class SongSet defines three fields for its objects.
Objects are uniquely identified by numbers: instance definition IDs at build time, and object IDs at runtime. In addition to these required IDs, each object can optionally have a text value called an object name.
Each class specifies how its objects handle their names. Some viewable classes display their objects' names to users. For example, the text on a button is the button's object name. Other classes don't display their objects' names to users. Names for these objects are useful only for programming and debugging.
Unlike an instance definition ID or object ID, an object name need not be unique. More than one object in an instance definition file or a running Magic Cap system can have the same object name. Because of this, you can't rely on just an object name, or even a class and object name, to uniquely identify an object in your package or in Magic Cap.
You can name an object in an instance definition by including the text of the name between the class name and the instance definition ID, like this:
Instance SongSet 'Trios' 421; firstSong: (Song 239); middleSong: (Song 'Zillionaire' 5); finale: (Song 882); End Instance;
The song set object defined by this example has an object name, `Trios'. In addition, the object referred to by the second field includes a name. An instance definition that refers to another object must use its name in the reference.
The names and types of fields for each class are specified in class definition files. For more information on the format of instance definitions, see Guide to CodeWarrior Magic.
Magic Cap provides two ways to make objects dynamically at runtime: you can create a copy of an existing object, or you can make a new, empty object. Magic Cap defines several operations for copying objects and several for creating new objects. You'll usually make new objects by copying existing ones because the operations that copy an object also copy the objects it refers to in its fields, which ensures that the new copy is fully functional. For example, when you copy a viewable object that includes another viewable object contained inside it, the contained object will also be copied.
The operations that copy objects and create new objects are defined by class Object. Because all classes in Magic Cap that create objects include class Object as an ancestor, you can use these calls to create any object you want.
You can use the NewNear and NewTransient operations to create new objects. Call NewTransient to create a new object in the package transient cluster.
When you call NewNear, you'll pass an example object. NewNear will then create a new object in the same cluster as the example object. For example, if you call NewNear with an example object in the package persistent cluster, the new object will also be in the package persistent cluster.
When you call NewNear or NewTransient to create a new object, Magic Cap creates the new object in memory, then calls the new object's Init operation to give the object a chance to set up its fields.
By default, when you create a new object of a package class, the object is created in a package cluster. Similarly, when you create a new object of a system class, the object is created in a system cluster, even if the code that creates the object is in a package.
Often, you'll create a new object by copying an existing one using the CopyNear or CopyTransient operations. These operations are analogous to the New operations described above. Their names indicate where the newly copied object will be created. These operations also copy any objects that the copied object refers to in its fields.
When you call CopyNear or CopyTransient to create a new object, Magic Cap creates the new object in memory, then calls the new object's Copying operation to give the object a chance to set up its fields. Because these operations also copy any objects referred to in the copied object's fields, Magic Cap calls the Copying operation of each newly copied object.
When you create objects dynamically with a New or Copy operation, the operation you call will return the object ID of the newly created object. You can use this object ID to address the object.
Users can check the new items go here box on PC cards in the storeroom to request that new objects be created on the PC card instead of in main RAM. To create objects that respect the user's setting of this check box, Magic Cap provides preferred container operations. You should use these operations to create new data objects that respect the user's setting.
The preferred container operations are NewPreferred, which creates a new object, and CopyPreferred, which copies an object from an existing one. These operations take a parameter, filingContainer, which contains the location where items from your package will be filed if the user packs up the new items package. You should pass your package's stack scene in the filingContainer parameter.
Because objects aren't stored on a stack as conventional variables are, objects don't follow the usual storage-class rules of procedural programming languages. Objects that you create while a function is executing are never automatically destroyed when the function ends, unlike automatic (local) variables in C. You must destroy them yourself. Any objects you create in a transient cluster might be destroyed at any time, such as when the communicator shuts off.
You can call Destroy to destroy an object explicitly and free its storage. Because Destroy is defined by class Object, you can use it with all Magic Cap objects. When you call Destroy, Magic Cap also destroys any objects that the given object refers to in its fields. Although Magic Cap provides a garbage collection system to delete unused objects, you must make sure that you destroy objects that you create and no longer need. In general, garbage collection is only triggered in critically low memory situations and when power is shutting off. You can't rely on garbage collection to free memory for you when you allocate a new object.
When you call Destroy to destroy an object, Magic Cap calls the given object's Finalize operation to give the object a chance to perform some action before it is deleted. Because Destroy also destroys any objects referred to in the given object's fields, Magic Cap calls the Finalize operation of each referenced object before it is destroyed.
When an object is about to be destroyed, Magic Cap calls Finalize on the given object first, then on any objects it refers to, and then destroys the given object and the objects it refers to. You can use the Finalize call to save referenced objects from being destroyed by setting the appropriate field to nilObject in your Finalize code. Then, when the given object is destroyed, the object you wanted to save will be left alone because it is no longer referenced by the destroyed object.
Instance definition IDs are only used in instance definition files. You can't refer to them from your source code. Because of this, you must take special steps to address statically created objects in source code. Most of the objects that you create statically should be designed to be self-sufficient; that is, you shouldn't have to refer to them from outside their own operations. Every operation can refer to its responder.
Your package may have a few special objects that you want to create statically, but still address with operations other than the object's own. To handle these kinds of objects, Magic Cap defines a form of global addressing that you can use to address your package's statically defined objects from your source code. In your instance definition file, you can specify the objects that you want to address from your source code. Then, in your source files, you can use the MakePackageIndexical macro provided by CodeWarrior Magic to define package indexicals, a form of object IDs that you can use in your source code to address your objects. Following is an example of a definition for a package indexical:
#define iMilkyWay MakePackageIndexical(26,4)
Because indexicals are similar to global variables, you should use them sparingly, as they have many of the same drawbacks as global variables. Magic Cap defines many system indexicals that provide access to well-known system objects. See the Indexicals section below for more information on system indexicals.
This section discusses the ways you can address objects in a Magic Cap package. Magic Cap provides various techniques for addressing objects both at build time in instance definition files and at runtime in source code.
Objects defined in instance definition files often refer to other objects. Magic Cap provides two ways for objects to refer to others at build time in instance definition files: instance definition IDs and indexicals.
Package objects in an instance definition file can address other objects directly by using instance definition IDs, as shown in this chapter's Creating Objects at Build Time section. This form of address only works for statically created objects in the same package. The instance definition file's syntax for this form of reference consists of placing the referenced objects' instance definition in parentheses when defining a field, like this:
// start of instance definition here firstSong: (Song 239); // another static object // rest of instance definition here
Package objects in an instance definition file can refer to well-known objects in the system or the package itself by using indexicals. You can have your package address an indexical by simply writing the indexical's symbolic name as the field's value, like this:
// start of instance definition here favoriteSound: iNewYearSong; // indexical reference // to system object // rest of instance definition here
Symbols for system indexicals, which are defined automatically when you build your package, always start with the letter i, as with the example iNewYearSong shown above. You should use names starting with ip as symbols for your package indexicals. The addition of p helps avoid potential name conflicts with system indexicals.
Magic Cap provides various ways for you to address objects from your package's source code by using various kinds of object IDs. The kinds of object IDs include indexicals, direct references, indirect references, class numbers, and lightweight objects.
Direct references address objects in the package's own clusters or in the system clusters. Direct references are the most common kind of object ID. They also provide the fastest access to objects, so you should use them whenever you can instead of the other kinds of object IDs described in this section.
When you create a new object by calling one of the New or Copy operations, the object ID returned by the operation will be a direct reference.
Indexicals provide a way to address well-known objects in the system, your package, and other packages. You can address indexicals defined by Magic Cap itself, or you can use indexicals to address important objects defined by other packages.
Indexicals are an indirect form of object ID. When you use an indexical, the object runtime maps the indexical to the appropriate object ID, then converts the object ID to a pointer to access the object.
Because indexicals use an extra level of indirection, they are somewhat less efficient than direct references, although this inefficiency isn't significant for most uses. If you use an indexical repeatedly, as in a tight loop, you can improve performance by using a direct reference instead of the indexical. The object runtime provides the DirectID operation, which converts an indexical to a direct or indirect reference. You can then use the direct or indirect reference for faster access to the object.
DirectID always returns the most efficient, most direct object ID for an object, and it's safe to call on all kinds of object IDs. If the reference can't be made more direct, DirectID simply returns the given object ID unchanged. This happens, for example, if you call DirectID on an object ID that is already a direct reference.
You should always call DirectID before comparing two object IDs, except in the rare case when you can guarantee that both object IDs are not indexicals. You don't ever have to call DirectID on the responder inside its own operation. The object runtime ensures that the responder is a direct reference inside its operations.
Magic Cap defines hundreds of system indexicals for addressing objects that include images, songs, text styles, windows, gadgets, and virtually all interesting system objects. Magic Cap defines symbolic names for most of these objects so that you can use indexicals to address them from your instance definition files and source code.
In addition to indexicals that address unchanging objects, you can use indexicals with values that change dynamically depending on the state of Magic Cap. These current indexicals let you address such dynamic objects as the current user, the scene that is visible on the screen, or the user's default stationery for new electronic mail messages.
If you want to capture the value of a current indexical, you can call DirectID on the indexical. DirectID will return a reference to the object that represents the current value of the indexical at the time you make the call.
You can find a complete list of system indexicals, including current indexicals, in the file Indexicals.h.
In addition to system indexicals, you can define package indexicals. Package indexicals are useful in two ways. First, you can define objects as indexicals in instance definition files, then address them from your source code. Second, objects that you define as package indexicals can be easily addressed by other packages.
Indexicals are primarily used to address well-known system objects and objects in other packages. You can use indexicals in all the ways you use other object IDs. Indexicals are most commonly used as values in objects' fields and as parameters to operation calls.
Magic Cap uses system indexicals to define a set of prototypes, example objects that you can copy and use. These prototypes are blank or empty versions of objects that are useful in your packages. Because these prototype objects are often fairly complex, copying one by using an indexical provides a quick way to create a whole set of objects that work together.
For example, the indexical iPrototypeAddressCard represents a new name card for a person and all its associated objects. You can call one of the Copy operations on this indexical to create a new name card for a person. The new card comes complete with labels for home and work phones, home and work addresses, and work fax. The name on the card is new person, and the image is the standard image for a person. Note that all these items on the new card are the same ones that appear when the user creates a new name card with the new button in the name cards. Also, note that by simply copying the prototype via its indexical, you've created a complex set of objects that represent a name card, including more than 10 different interrelated objects.
You can use package indexicals to create prototypes for objects that you use often in your packages. This technique is most effective for complex objects that refer to many other objects. You shouldn't use prototypes for very simple objects. Instead, just use one of the New operations to create the objects, then fill in the appropriate fields.
When you use an indexical, you can't assume that you know the type of the object that the indexical addresses. Any subclass of the expected type might be substituted instead. For example, if you use an indexical that refers to a window, the actual object addressed might be an instance of a subclass of class Window. In general, you should never assume the class of any object. Instead, you can check to see whether an object implements the interface of a given class. See Guide to CodeWarrior Magic for more information about inheritance.
When you create your own package indexicals, remember that indexicals provide a level of indirection that you should consider when choosing what to name them. Try to choose names for your indexicals that refer to high-level architectural concepts, rather than specific implementation details.
Class numbers are a special form of object ID that provide sa way to refer to classes directly. You can use class numbers for some operations which can be executed without requiring a responder of the class.
The object runtime provides indirect references as a way to address package objects in other package's clusters. You'll get an indirect reference when you use an object from another package, such as when you read a field or use a parameter of an operation of a foreign object.
You can also get an indirect reference to an object in another package's cluster by calling Import on the desired object. Import returns an indirect reference to the given object, which you can then use to address the foreign object from your package's code. You can also create an indirect reference so that one of your package's objects can be used in another cluster; that is, to export your object. For more information about indirect references and using objects of foreign packages, see the Interpackage Operability section of this book's Software Packages chapter.
Every Magic Cap object carries several bytes of overhead for the object's ID, size, flags, and other information. Because of this overhead, it is impractical to use objects for structures that contain very little data, such as a single integer or character. To provide an efficient way to encode these structures as objects, the object runtime defines lightweight objects that don't carry all the overhead of full objects.
The object runtime defines a special lightweight object ID that represents the absence of an object. This ID is defined by the symbol nilObject.
Every class defines interfaces to its actions called operations. You can call these operations to perform actions on the class's objects. The operations of a class are defined by declaring them in the class's definition in a class definition file.
Many objects contain values that can be set and changed. For example, every viewable object includes a Boolean value that indicates whether it can be moved by the user and a pair of 32-bit values that indicate the object's height and width. You can specify values like this as attributes of the object in the class's definition.
Taken together, operations and attributes form the public interface of a class. Fields, which are often used to implement attributes, are not part of a class's public interface. They should be considered private and should only be used when defining objects in instance definition files and within code of operations of the class.
Operations for each class are defined in class definition files. In the class's definition, operations are defined with the keyword operation, as in the following examples:
operation StartSongs(); operation DrawWithContents(canvas: Canvas; clip: Path); operation DirectID(): Object;
Each operation definition begins with the keyword operation, followed by the name of the operation, and the operation's parameters in parentheses. If the operation returns a value, the definition ends with a colon and the type of the return value. All operation definitions end with a semicolon, like all lines in an ObjectMaker input file. For historical reasons, the syntax for defining operations and parameters in ObjectMaker is similar to that of Pascal.
You can define an attribute if you want to specify a value that can be read and set, as in the following examples:
attribute Last: Object; attribute CanExtendBottom: Boolean; attribute ContentHeight: Micron;
When you define an attribute in a class definition, you implicitly define two operations, one to get the value and another to set it, called a getter and a setter. Attributes simplify class definitions by grouping the getter and setter operations. Because each attribute defines two operations, attributes are part of the class's interface. Attribute definitions are simpler than operation definitions because attributes have no parameters.
The getter has the same name as the attribute. The setter prefixes "Set" to the attribute name. The getter takes no parameters other than the responder, and returns the value of the requested attribute. The setter takes the new value as a parameter, and returns no value.
The getter and setter operations created implicitly by the last attribute definition shown above work as if they were defined as follows:
operation ContentHeight(): Micron; // getter operation SetContentHeight(newValue: Micron); // setter
ObjectMaker defines various keywords that can be used optionally to modify operation and attribute definitions. See Guide to CodeWarrior Magic for complete information on the syntax of operation definitions.
Attributes provide an interface to their getter and setter operations, but you must provide an implementation for these operations. Because attributes often use fields to hold their values, ObjectMaker lets you define automatic field accessors as a way to connect attributes to fields automatically, without having to write any getter or setter methods yourself.
An automatic field accessor that reads the value of a field is called an automatic getter, and an automatic field accessor that sets the value is called an automatic setter. When you use automatic field accessors, you don't have to write any code to implement the attribute's operations - the code is created for you.
If you don't use automatic field accessors to implement the operations defined by an attribute, you must one of the other techniques for implementing operations: C functions or scripted functions.
See this chapter's Automatic Field Accessors section for complete information about creating automatic field accessors, including syntax for class definition files.
You ask a class to perform an action on one of its objects by calling an operation defined by the class or one of its superclasses. You call an operation by making an ordinary function call with the same name as the operation, passing the object's ID as the first parameter. For example, every viewable object includes a sound, and you can play the sound by calling the viewable's PlaySound operation. Assuming that viewObject is defined as an object ID, you can call its PlaySound operation as follows:
PlaySound(viewObject);
PlaySound is the name of the operation as defined by class Viewable. In this example, viewObject is the responder.
When an operation is called, the object runtime performs an action in response. The object runtime determines what action to take by examining the class of the responder through a process called dispatching, handled by a part of the object runtime called the dispatcher. The dispatcher first checks to see if the responder's class implements the operation. If so, the dispatcher executes that class's implementation of the operation.
If the responder's class doesn't implement the operation, the dispatcher searches through the class's ancestors, beginning with the class's immediate superclass. When the dispatcher finds a class in the class's inheritance hierarchy that implements the operation, the object runtime executes that class's implementation. If no class implements the operation, the object runtime generates a method not found error, which activates the debugger in debug versions of Magic Cap, or continues silently in non-debug versions. You can use the Implements operation before calling an operation to determine if an object implements a particular operation.
You can see the order that the dispatcher will use to search for a class's operation by using Magic Cap's inspector to look at an object of the class. The inspector displays the object's inherited classes in reverse of the order that the dispatcher searches.
Classes can define operations that don't require an instance of the class in order to execute. These operations are called simple intrinsics. Simple intrinsics are faster to invoke than other operations but can't be overridden by subclasses. Utility operations and other global functions are often implemented as simple intrinsics. Simple intrinsics do not take a responder as a parameter.
For example, operation LocalTime, which returns the communicator's time setting, is implemented as a simple intrinsic. When you call LocalTime, you don't pass a responder as a parameter.
When you create a class and operations, you can choose from among several ways to implement each operation that your class defines:
You use the same syntax to call an operation no matter which of these techniques is used to implement an operation. In fact, the choice for the operation's implementation isn't readily visible to callers.
The following sections discuss each of these techniques for implementing an operation.
By far the most common way to implement an operation is to write a function in your C source. To do this, you define a C function that has as its name the class name and operation name connected with an underscore. For example, a C function named TestClass_DoSomething would provide the code for the DoSomething operation of class TestClass. When you use this technique to implement an operation, the code in your C function is called a method.
Following is an example of the PlaySound operation, shown above, implemented with a C function:
Method void Viewable_PlaySound(ObjectID self) { ObjectID sound = Sound(self); if (sound != nilObject) Play(sound); }
To use a C function to define a method, start the function declaration with the identifier Method, a macro defined by CodeWarrior Magic, followed by the rest of the normal function declaration. The function name should be the class name and the operation name, as specified in the class definition file, connected with an underscore.
Remember that when you call an operation, you always pass the responder as the first parameter:
PlaySound(viewObject);
However, the responder is never shown when the method's operation is defined in the class definition file. The reason why the responder isn't shown in the definition files is simply to make the definitions easier to read and to type. The responder is always implied in the class definition file and must be used when you call an operation or declare a C function as a method.
For example, the operation PlaySound shown above would be defined as follows in a class definition file:
operation PlaySound();
Note that there's no mention of the responder in the definition, even though you must always pass it, as shown in the example call. For comparison, here's how the same call would appear in Object Pascal, a popular object-oriented language:
viewObject.PlaySound; (* won't work in Magic Cap *)
In C++, the call would look like this:
viewObject.PlaySound() /* won't work in Magic Cap */
The Magic Cap version in C is repeated here for comparison:
PlaySound(viewObject); /* works in Magic Cap */
Note that Object Pascal and C++ don't require you to pass the responder explicitly. Instead, it is passed implicitly in every operation call. Because Magic Cap programs are created with a standard C compiler, rather than a compiler for an object-oriented language, you must pass the responder explicitly.
This difference between the appearance of operations in C and class definition files can cause build-time errors that may be confusing. Because this point is so important, it's repeated and summarized in the following warning.
WARNING: When an operation is declared or called in C source, the first parameter must be the responder, the object whose operation is being called. When an operation is defined in a class definition file (ObjectMaker source), the responder is implied but is never shown. For example, this ObjectMaker definition:
operation PlaySound(); // ObjectMaker syntax
// the responder is implied, not shown
matches an operation declared in C like this:
Method void
Viewable_PlaySound(ObjectID self) // C syntax
// the responder is explicitly shown
Here's how you would call the same operation in C:
PlaySound(viewObject); // C syntax
// the responder must be included
When you're writing an implementation of an operation, you can use the first parameter to refer to the responder. By convention, this parameter is usually named self, but there's no requirement that it have that name.
You can use a script written in Magic Script or Telescript to provide the implementation for an operation. You can provide a Magic Script version of any operation. See Guide to CodeWarrior Magic for more information on using scripts for operations.
All objects in memory are collected into groups called clusters. A cluster is associated with a contiguous range of memory addresses and manages all the objects in that range. Every object belongs to exactly one cluster. Like most structures in Magic Cap, clusters are themselves objects. This section describes clusters and the way packages use them.
The object runtime provides a way to work with a section of memory that shares some hardware characteristic. For example, it is sometimes useful to have a way to address all persistent RAM, rather than dividing it into a system persistent cluster and many package persistent clusters. To accomplish this, Magic Cap defines a special kind of cluster called a metacluster. The features of metaclusters are almost the same as those of clusters. Metaclusters typically refer to memory that shares some hardware characteristic, such as all persistent RAM, all transient RAM, or all the memory on a PC card.
The elements of metaclusters are other objects, usually clusters. For example, the persistent RAM metacluster usually contains as elements the system persistent cluster and the package persistent clusters. Metaclusters can include any other objects as elements, not just clusters.
Each object in a cluster occupies a contiguous area of memory. As new objects are created and others are destroyed, the memory in a cluster can become fragmented, with many free spaces occurring between the objects. Because each object requires a contiguous space in memory, a fragmented cluster effectively reduces the amount of available memory. To relieve this problem, Magic Cap moves objects at reasonable times to close up the gaps in free space.
Each cluster includes a pointer to every object in the cluster. These pointers are grouped together in a single memory block near the start of the cluster. When an object is relocated, the cluster's pointer to that object is updated to reflect the object's new location. This pointer is called the object's master pointer, and the group of master pointers is called a master block. Magic Cap accesses objects by converting object IDs to master pointers when they are used.
Magic Cap stores many objects in ROM that users must be able to customize and change. To allow Magic Cap and software packages to change these objects, the object runtime can store changed versions of ROM-based objects in a special RAM cluster that is associated with the ROM cluster. This RAM cluster that holds changed versions of objects is called a shadow cluster.
Every ROM cluster that allows changes to its objects has a shadow cluster associated with it. When Magic Cap or a package makes changes to an object stored in ROM, the object runtime modifies a copy of the object in the shadow cluster instead. If the object is changed again, the copy in the shadow cluster is modified each time the object is changed.
Shadow clusters are typically kept in transient RAM. Because the contents of transient RAM are lost when the communicator shuts off, the object runtime periodically preserves the objects in the shadow cluster by transferring them to another shadow cluster in persistent RAM. This process of transferring objects from the transient shadow cluster to the persistent shadow cluster is called committing the changed objects. The objects themselves are called committed changes. In contrast, objects in the transient cluster are called uncommitted changes.
The object runtime commits changes at various times. In particular, changes are committed whenever the user goes to a new scene and whenever the communicator shuts off. If the communicator unexpectedly loses power, uncommitted changes in the current scene are lost. Magic Cap's revert command in the Magic Lamp works by destroying uncommitted changes.
Note that the system objects stored in ROM actually may be found in any of three clusters: the system source cluster in ROM; the system shadow cluster in RAM, which contains committed changes to the ROM; and the system changes cluster in RAM, which contains uncommitted changes to the ROM. These three clusters form a chain.
When you address an object, the object runtime determines if the object you're addressing exists in a cluster that has one or more shadow clusters. If so, the cluster with uncommitted changes has the most recent versions of objects, followed by the committed changes, and finally the source. The object runtime automatically finds the current version of the object from among these clusters.
The object runtime has two three-cluster chains: the system persistent chain and the package persistent chain. In each case, the first cluster in the chain contains uncommitted changes, the middle cluster has committed changes, and the last cluster holds the original versions of the objects.
Note that the same object ID is used for all versions of the object, so it is impossible to directly address one particular version. The object ID always addresses the current value of the object.
Every package defines a set of clusters in which it can address objects with direct references, as well as additional clusters that it works with. This set of clusters is defined by a context. At any time, the current context determines which context is active, and therefore which clusters can be used. The current context is the native context of the executing code. When a package's code is executing, the package's context is current. In addition to the context for every package, Magic Cap defines a system context that is current when system code is executing.
Magic Cap sets up a context for each package. Each package's context includes slots for clusters and metaclusters; objects in the first 16 slots in each context can be addressed by direct references in the package. Every package's context contains several important clusters, including the system persistent, system transient, package persistent, and package transient clusters. Metaclusters included in every package's context are the persistent RAM metacluster, transient RAM metacluster, one metacluster for each inserted PC card, and a metacluster for all non-system objects in ROM (usually bundled packages and custom servers in ROM).
A context can have any number of clusters, but only objects in the first 16 clusters can be addressed directly by the package. Not every slot in every package's context is filled, although the first 20 slots are reserved for clusters with well-defined purposes, as described below.
Every slot in a context contains one cluster or metacluster. There are three ways to refer to the clusters that fill the slots in a context, as follows:
This section lists the first 20 slots available in each context and presents information about the cluster in each slot.
This cluster contains uncommitted changes for the system source cluster (it's the shadow cluster for the system source cluster in ROM) and any other persistent objects of system classes. When a ROM-based system object is changed, the changed version is created in this cluster. The object runtime looks here first when searching for any system persistent objects, then in the system shadow cluster, and finally in the system source cluster (the ROM) itself.
If your package creates permanent data using objects of system classes, those objects will be created in this cluster. You'll probably examine the contents of this cluster when you want to observe system objects while debugging.
Direct reference object IDs for objects in this cluster begin with hexadecimal digits $80 through $87.
This cluster is used in special situations for executable code, such as packages received from a remote source via a communication connection or code copied into RAM. Objects in this cluster can't move in memory. You'll rarely use this cluster directly or examine it when debugging.
Direct reference object IDs for objects in this cluster begin with hexadecimal digits $88 through $8F.
This metacluster contains clusters and related objects stored on a card inserted in the second PCMCIA slot. Not every Magic Cap communicator has a second PCMCIA slot. This context position is unused if the communicator has only one PCMCIA slot or its second slot is empty. You'll rarely use this cluster directly or examine it when debugging.
Direct reference object IDs for objects in this cluster begin with hexadecimal digits $90 through $97.
This cluster is reserved for use by Magic Cap in future versions.
This metacluster contains all clusters stored in persistent RAM, along with some related objects. You might examine this cluster when debugging.
Direct reference object IDs for objects in this cluster begin with hexadecimal digits $A0 through $A7.
This metacluster contains all clusters stored in transient RAM, along with some related objects. You'll rarely use this cluster directly or examine it when debugging.
Direct reference object IDs for objects in this cluster begin with hexadecimal digits $A8 through $AF.
This cluster contains uncommitted changes for the package source cluster and any other persistent objects of your package's classes. When an existing package object is changed, the changed version is created in this cluster. The object runtime looks here first when searching for any package persistent objects, then in the package shadow cluster, and finally in the package source cluster itself.
If your package creates permanent data using objects of package classes, those objects will be created in this cluster. You'll probably examine the contents of this cluster when you want to observe package objects while debugging.
Direct reference object IDs for objects in this cluster begin with hexadecimal digits $B0 through $B7.
This cluster is reserved for use by packages in future versions of Magic Cap.
When you create transient objects of package classes, they are in this cluster. You'll typically find few objects in this cluster. You may find more if you use transient objects heavily. You might examine the contents of this cluster if you want to observe transient package objects when debugging.
Direct reference object IDs for objects in this cluster begin with hexadecimal digits $C0 through $C7.
This metacluster contains all clusters stored in vendor ROM, the portion of ROM customized by the manufacturer of the communicator and isn't part of Magic Cap itself. Vendor ROM typically contains bundled packages, servers to control manufacturer-specific hardware, and other custom software. As with all metaclusters, the vendor ROM metacluster contains a few other objects related to the clusters. You'll rarely use this cluster directly or examine it when debugging.
Direct reference object IDs for objects in this cluster begin with hexadecimal digits $C8 through $CF.
When you create transient objects of system classes, they are in this cluster. This cluster contains many objects because Magic Cap and many packages create many transient system objects. You might examine the contents of this cluster if you want to observe transient system objects when debugging.
Direct reference object IDs for objects in this cluster begin with hexadecimal digits $D0 through $D7.
This cluster is reserved for use in future versions of Magic Cap.
This metacluster contains clusters and related objects stored on a card inserted in the first PCMCIA slot. Every Magic Cap communicator has at least one PCMCIA slot. This context position is unused only if the communicator's first (or only) PCMCIA slot is empty. You'll rarely use this cluster directly or examine it when debugging.
Direct reference object IDs for objects in this cluster begin with hexadecimal digits $E0 through $E7.
This cluster contains transient objects that can't move in memory. Objects in the cluster are useful as buffers for communication and other data transfer operations. To create a buffer that will be used, then quickly destroyed, call NewTransientBuffer to create a buffer object in this cluster and DestroyTransientBuffer when the buffer is no longer needed. For buffers that will not be discarded quickly, call NewLockedBuffer to create the buffer and DestroyLockedBuffer to destroy it. If you create locked buffers, you might examine them in this cluster when debugging.
Direct reference object IDs for objects in this cluster begin with hexadecimal digits $E8 through $EF.
This cluster contains objects that have been created by the current Telescript process, including any objects created by an executing Magic Script. You'll rarely use this cluster directly or examine it when debugging.
Direct reference object IDs for objects in this cluster begin with hexadecimal digits $F0 through $F7.
This cluster is reserved for use by Magic Cap in future versions.
This slot contains the system source cluster, a metacluster that includes most of the system objects in ROM, as well as the clusters that manage the system ROM. Objects in this cluster are modified by shadow objects in the system shadow cluster and system changes cluster. If an object isn't present in one of those clusters, its value is found in this cluster. You might examine the contents of this cluster when debugging if you want to observe the original values of system objects.
This slot contains the system shadow cluster, a cluster that holds committed changes to the system objects in ROM. Objects in this cluster are persistent modifications to system objects in ROM. If an object is present in this cluster, its value in ROM is ignored. You might examine the contents of this cluster when debugging if you want to observe the values of system objects that have been permanently modified.
This slot is occupied by the package source cluster, which contains the original values of the package's persistent objects. Objects in this cluster are modified by shadow objects in the package shadow cluster and package changes cluster. If an object isn't present in one of those clusters, its value is found in this cluster. You might examine the contents of this cluster when debugging if you want to observe the original values of package objects.
This slot contains the package shadow cluster, a cluster that holds committed changes to package objects in persistent RAM. Objects in this cluster are persistent modifications to package objects in the package source cluster; if an object is present in this cluster, its value in the package source cluster is ignored. You'll probably examine the contents of this cluster when you want to observe package objects that have been permanently modified.
When you refer to a package object, the object's ID in its own context is a direct reference, which differs from its ID in a foreign context, which is an indirect reference. The object runtime sometimes translates object IDs automatically to the current context when necessary. Specifically, this happens when you call an operation and when you use the field access operations to read or modify an object's fields.
If you call an operation with an indirect reference as the responder, the object runtime will automatically switch the current context to the operation's context. The responder's ID and the IDs of any parameters that are objects will be automatically translated to be appropriate for the operation's context. Because of this automatic translation, you can use the responder and any parameters inside an operation without having to import or otherwise translate their object IDs.
Some operations define a parameter block, a structure of type Parameters. Object IDs in parameter blocks, like normal parameters, are also translated automatically into the current context.
When you use field access operations, such as Field and SetField, to read and modify an object's fields, any object IDs in the fields are translated to the current context automatically. For more information on field access, see Accessing Fields, below.
Some operations are designed to call other functions passed as parameters, usually to perform some repetitive action on a set of objects. For example, class Linkable defines the following operation:
operation Each(function: EachFunction; VAR parameters: Parameters): Object
This operation calls a function repeatedly for each element in the linked list that the responder belongs to. The function that is called repeatedly is known as a callback function, because Each calls it, rather than your package calling it directly.
Unlike an operation, when a callback function is executing, the object runtime doesn't necessarily switch contexts to make sure the context is correct for the callback function when it executes.
The responder, any parameters that are object IDs, and any parameter block object IDs are automatically translated for the current context, so they can be used without knowing the current context. However, the callback function should not use any other object IDs without ensuring that they are appropriate for the current context.
To make sure that object IDs are appropriate for the context, your callback function can consist entirely of a single operation call. Operation calls, unlike callback functions, always execute in their own context, so this technique will force the object runtime to switch to your context, ensuring that your callback function won't use improper object references. With this technique, you can address objects normally.
If you write code that must use indirect references repeatedly, as in a tight loop, you can improve performance by explicitly switching to the objects' native context. By switching to this context, your code can use direct references, which are handled more quickly than indirect references.
Given an indirect reference, you can call its PushReferenceContext operation to switch to the context that contains the referenced object. To return to the original context, call PopContext.
You can also switch to the context of an object by calling its PushContext operation to save the current context and switch to the object's context. As with PushReferenceContext, you can return to the original context by calling PopContext.
Calling PushReferenceContext is equivalent to calling PushContext and then Import on the the reference.
You can determine the context of any object by calling its Context operation. Context returns the ID of the given object's context.
Every object in memory consists of a series of fields containing values. Objects include the fields defined by their classes and superclasses. The fields of objects are private. You should avoid reading or changing the field, except from operations defined by the field's class. There are two fundamental ways to read from or write to the fields of an object:
Whenever possible, you should work with fields by calling operations that access the field for you. In cases where that isn't possible, or when you must work with the fields of your own objects, you'll use accessor operations defined by the object runtime to read and change fields.
A class's fields are defined along with the rest of a class in a class definition file. The definition for a class includes the field keyword, the name of the field, its type, and optional modifiers.
Each object's fields are organized into groups, with each group defined by one of the classes in the object's class ancestry. Not every class defines fields. For example, Magic Cap defines class StatusAnnouncement, which inherits from class Announcement, which in turn inherits from class Object. Every object of class StatusAnnouncement includes the following fields:
Every object can have one variable-length area, called the object's extra data. The extra data can be any information that can change its length at runtime. For example, Magic Cap defines many classes that include lists of object IDs. These lists are usually kept in the objects' extra data, because the size of the extra data can vary as the list adds or removes members.
Because every object has only one extra data area, only one class in an object's class ancestry can use extra data. Once a class defines an extra data area, no subclass of that class can define another extra data area for its objects.
You should avoid reading and writing fields directly, instead using operations wherever possible. To help enforce this behavior, you can ask ObjectMaker to create operations automatically that read and write a field associated with an attribute: automatic field accessors. You can define these operations for a field by using the following syntax in your class definition file:
// start of class definition here field justAboutGlad: Boolean, getter, setter; // other fields defined here attribute JustAboutGlad: Boolean; // rest of class definition here
The attribute declaration automatically specifies two operations. If you want to keep the values of this attribute in a field, you can create automatic field accessors (an automatic getter and setter), for the attribute. The automatic getter simply returns the value of the field. The automatic setter just sets the field to a value that you pass when you call it.
To create automatic field accessors, use the same name for the field and the attribute, capitalizing the attribute name.
You can use this technique to create an automatic setter and getter for any attribute and associated field that contain an object, unsigned, signed, or boolean value. When you create automatic field accessors, they can be overridden by subclasses just like other kinds of operations, and the overridden versions can be C functions.
You should only create getters and setters for fields that must provide a public interface. Most fields can safely be kept private, and so don't need getters and setters. You might want to create an interface for some fields that allows their values to be read, but not changed. To do this, use only the getter keyword in the field's definition; only a getter operation will be created.
If no operation is defined to access a field, you can use accessor operations to read and write the field directly. The object runtime defines four families of accessor operations:
Many of the accessor operations require that you pass a field number, a value that identifies the field within its class. To help you pass field numbers to accessors, ObjectMaker defines symbolic names for all fields at build time. The symbolic name for a field number consists of its class name, followed by an underscore, followed by the field name. The following are all examples of symbolic names for fields:
Viewable_subview Game_currentInning Form_image Shelf_shelfBorder Angel_timeOnHold
In addition to these field numbers, ObjectMaker also defines local field numbers for all fields at build time. Local field numbers are used by accessors that always operate on the responder's fields. The symbolic name for a local field number consists of just the field's name, with no reference to its class, as follows:
formItems // a field of class Form superview // a field of class Viewable length // a field of class AbstractList
When a field contains an object ID, the object ID is likely to be a direct reference in the context of the object that contains it. If the current context is different, the object ID can't be used. To make sure that an object ID from a field can be used in the current context, the object runtime defines the MakeUsable operation of class Object. Before using an object ID stored in a field, you must make the object usable with the MakeUsable operation.
Similarly, class Object defines a MakeStorable operation to convert an object ID before storing it into a field. MakeStorable ensures that the object ID is a direct reference in the context of the object that stores it. Before storing an object ID in a field, you must make the object storable with the MakeStorable operation.
Depending on the accessor you use, you may not have to call the conversion operations MakeUsable or MakeStorable; some accessors call them for you automatically. Specifically, the single-field accessors call the conversion operations for you automatically. When you use whole-object accessors, you must call the conversion operations directly. When you use extra-data accessors to read object IDs in extra data, you must also call the conversion operations directly.
To modify an object ID in a field you'll typically follow this sequence:
The following sections describe the operations in each of the four families of accessors and discuss when you might use them.
You can call a single-field accessor to read or set the value of any field that contains an object ID or a value of type Unsigned, Signed, UnsignedShort, SignedShort, or Boolean. To access a field of any other type, you must use whole-object or direct accessors. See the appropriate sections below for details.
These are the single-field accessors:
Call Field to get the value of a field from within a method of that object's class. Field takes two parameters: the responder, which you normally address in your C source as self, and the local field number of the field that has its value read. Field returns the value of the field as its function result.
Similarly, you can call SetField to set the value of a field of an object from within a method of that object's class. SetField takes the responder, which should be self, the local field number, and the field's new value as parameters.
Following are examples of Field and SetField:
corridorSize = Field(self, corridorSize); ObjectID rightArrow = Field(self, rightArrow); return ((Field(self, dwFlags) & kBunnyShowingMask) != 0); SetField(self, mode, 0); SetField(self, numDrawsLong, Field(self, numDrawsLong) + 1);
You can use Field and SetField only to work with fields of objects from within the methods of that object's class; that is, the first parameter must always be the responder. For other objects, call FieldOf to get the field's value and SetFieldOf to set its value.
When you call Field or SetField, you can only read or set the fields that are defined by the object's class - not any of its superclasses. For example, if you call Field with an object of class Box, you can only access fields defined by that class, not by its superclasses, Viewable or Linkable.
FieldOf takes two parameters: the object that has its field read, and the field number of the field. FieldOf returns the value of the field as its function result. SetFieldOf takes three parameters: the object, the field number to be set, and the new value.
These are examples of FieldOf and SetFieldOf:
return FieldOf(self, HasBorder_border); userLevelList = FieldOf(iCommandWindow, CommandWindow_userLevelList); SetFieldOf(self, NameBar_pageArrow, FieldOf(iNameBar, NameBar_pageArrow)); titleWidth = StringWidth(title, FieldOf(self, Viewable_labelStyle)); SetFieldOf(self, Shelf_shelfBorder_h, newBorderSize); SetFieldOf(iDesk, Scene_screen, officeScreen);
Note that Field and SetField always work on the responder and take a local field number (just the field name), while FieldOf and SetFieldOf work on any object and take a field number (the class name, an underscore, and the field name).
When you call FieldOf or SetFieldOf, you can only read or set the fields that are defined by the class you specify- not any of its superclasses.
All single-field accessors automatically call MakeUsable or MakeStorable if necessary. You shouldn't call them yourself when you use single-field accessors to work with fields.
When you change an object, the object may be shadowed, using up precious RAM for the shadowed version. If you call SetField or SetFieldOf without changing the value of the field, the object runtime doesn't actually modify the object, so no shadow object is created. Because of this, you can call SetField or SetFieldOf without checking to see if the field will actually change. The object runtime will avoid modifying or shadowing the object unnecessarily.
Field and FieldOf return unsigned values. You should cast the returned value if you want to interpret it as a signed short integer.
You can use a whole-object accessor to read or set the values of any fields of an object. Whole-object accessors provide a way to use fields of types other than those that work with the single-object accessors; that is, types other than Object, Unsigned, Signed, UnsignedShort, SignedShort, and Boolean. Whole-object accessors are also efficient when you want to read or set more than one or two fields of an object.
Whole-object accessors work by reading or setting all the fields of an object at once. To set one or more fields of an object, you'll typically call an accessor to read all the fields of an object, change the fields you want, then call another accessor to write the changes back.
These are the whole-object accessors:
Call ReadFields to get the values of all the fields of an object from within a method of that object's class. ReadFields takes two parameters: the responder, which should be self, and a pointer to storage for the field values.
Similarly, you can call WriteFields to set the values of all the fields of an object from within a method of that object's class. WriteFields takes the same two parameters as ReadFields: the responder, which should be self, and a pointer to the storage for the field values.
To help you create storage for the field values when calling ReadFields and WriteFields, ObjectMaker defines a symbolic name for each class that specifies a structure containing the class's fields. The symbolic name for this structure is the class name, followed by an underscore and the word fields. You can use this symbolic name in your C source to allocate storage for the fields as follows:
ClownStrike_Fields fields; // allocate a structure // to hold the fields' // values
Once you've created a structure to store the fields, you can use the whole-object accessors to work with the object. Assume that class ClownStrike is defined as follows in a class definition file:
Define Class ClownStrike; field strikeStart: Unsigned; // unsigned long - // 32 bits field strikeLevel: SignedByte; // field of unusual // size - 8 bits field onStrike: Boolean; // Boolean - 1 bit End Class;
The following example shows how to change the value of field strikeLevel. Because this field doesn't occupy exactly 1, 16, or 32 bits, you can't use the single-field accessors described above. Instead, the example shows how to use whole-object accessors to change the field.
ReadFields(self, &fields); // read all fields fields.strikeLevel += 1; // increment 8-bit field WriteFields(self, &fields); // write fields back // to object
The example first reads the values from the object into the storage that fields defines. Then, the value of field strikeLevel is incremented in memory. Finally, the values of the fields in memory are stored in the object.
When you call ReadFields or WriteFields, you can only read or set the fields that are defined by the object's class - not any of its superclasses. For example, if you call ReadFields with an object of class Box, you can only access fields defined by that class, not by its superclasses, Viewable or Linkable.
You can use ReadFields and WriteFields only to work with objects from within the methods of that object's class; that is, the first parameter must always be the responder. For other objects, call ReadFieldsOf to get the values of the object's fields and WriteFieldsOf to write the new values to the fields.
ReadFieldsOf takes three parameters: the object from which the fields are read, the class number of the object's class, and a pointer to storage for the field values. WriteFieldsOf takes three the same three parameters: the object to which the fields are written, the class number of the object's class, and a pointer to storage for the field values.
When you call ReadFieldsOf or WriteFieldsOf, you can only read or set the fields that are defined by the class you specify- not any of its superclasses.
Unlike single-field accessors, whole-object accessors do not automatically call MakeUsable or MakeStorable when necessary. You must call them yourself if you're reading or writing fields that contain object IDs. See this chapter's Making Objects Usable and Storable section for more information.
When you change an object, the object may be shadowed, depending on its cluster. If you call WriteFields or WriteFieldsOf without changing the value of a field, the object runtime accesses the object anyway, and a shadow object is created, using up precious RAM for the shadowed copy even though the object hasn't changed. Because of this, you should be careful to avoid calling WriteFields or WriteFieldsOf if you haven't actually changed a field.
For more efficient access to an object's fields, you can use direct accessors to get a pointer to the fields. You can then use that pointer to get direct access to the object's fields, which you can then read or change. Direct accessors are more efficient than whole-object accessors because they don't cause reading or writing of all the object's fields.
To read or change one or more fields of an object with direct accessors, you'll typically call an accessor to get a pointer to the object, read or change the fields you want, then call another accessor to signify that you're finished with the pointer. There are two sets of direct accessor operations: one set that provides pointers that can be used to read and change an object's fields, and another set that provides pointers that can only be used to read an object's fields.
These are the direct accessors:
Call BeginReadFields to get a pointer to the object's fields when you only want to read fields, not change them. BeginReadFields takes one parameter, which must be the responder. BeginReadFields returns an untyped pointer that holds the address of the object's fields. This pointer can only be used to read the fields, not change them. After you're done reading the object's fields, call EndReadFields to finish the access.
After you've called the accessor to begin working with the object's fields, the object is accessed. It is temporarily prevented from being moved by the object runtime until you finish the access. Because objects always occupy contiguous memory, any new memory allocations have to work around the immobile object, which reduces the amount of memory new objects can occupy and hinders the work of the object runtime.
To avoid these allocation problems, you should never perform any action that allocates new objects in memory while an object is accessed. Many common actions, including calling almost any operations, can cause memory to be allocated. Because of this severe restriction, you should only use direct accessors when absolutely necessary, such as when you must access an object's extra data. When you must use direct accessors, you should minimize the period during which the object is accessed by doing as much work as possible before beginning the access or after finishing the access.
Call BeginModifyFields to get a pointer to the object's fields when you want to read or change fields. BeginModifyFields takes one parameter, which must be the responder. BeginModifyFields returns an untyped pointer that holds the address of the object's fields. This pointer can be used to read or change the fields. After you're done reading and changing the object's fields, call EndModifyFields to finish the access.
To help you create storage for the field values when calling BeginReadFields and BeginModifyFields, ObjectMaker defines a symbolic name for each class that specifies a structure containing the class's fields. The symbolic name for this structure is the class name, followed by an underscore and the word fields. You can use this symbolic name in your C source to allocate storage for the fields as follows:
BigShark_Fields fields; // allocate a structure // to hold the // fields' values
When you call BeginReadFields or BeginModifyFields, you can only read or change the fields that are defined by the object's class - not any of its superclasses. For example, if you call BeginReadFields with an object of class Box, you can only read fields defined by that class, not by its superclasses, Viewable or Linkable. Don't assume that you can use a pointer provided by a direct accessor to get to the fields defined by a superclass or to an object's extra data - the object runtime may store these fields and extra data separately in future versions of Magic Cap.
You can use BeginReadFields and BeginModifyFields only to access objects from within the methods of that object's class; that is, the first parameter should be self. For other objects, call BeginReadFieldsOf or BeginModifyFieldsOf to get pointers to the objects' fields.
BeginReadFieldsOf takes two parameters: the object from which the fields are read and the class number of the object's class. BeginModifyFieldsOf takes the same two parameters. Both operations return untyped pointers to the object that is accessed.
When you access an object with direct accessors, you can only read or change the fields that are defined by the class you specify- not any of its superclasses.
As with whole-object accessors, direct accessors do not automatically call MakeUsable or MakeStorable. You must call them yourself if you're reading or writing fields that contain object IDs. Remember not to call MakeUsable and MakeStorable while an object is accessed. See this chapter's Making Objects Usable and Storable section for more information.
When you change an object, the object may be shadowed, depending on its cluster. If you use BeginModifyFields or BeginModifyFieldsOf without changing the value of a field, the object runtime accesses the object anyway, and a shadow object is created, using up precious RAM for the shadowed copy even though the object hasn't changed. Because of this, you should be sure to call BeginReadFields or BeginReadFieldsOf if you're only reading fields.
Every object can have extra data, variable-length information that isn't stored in its fields. You can use extra-data accessors to get a pointer to the extra data. You can then use that pointer to read or change the object's extra data.
To read or change an object's extra data, you'll typically call an accessor to get a pointer to the extra data, read or change it, then call another accessor to signify that you're finished with the pointer. There are two pairs of extra-data accessor operations: one pair that provides pointers that can be used to read and change an object's extra data, and another pair that provides pointers that can only be used to read an object's extra data.
These are the extra-data accessors:
Call BeginReadExtra to get a pointer to the object's extra data when you only want to read extra data, not change it. BeginReadExtra takes one parameter, the object which will have its extra data read. BeginReadExtra returns an untyped pointer that holds the address of the object's extra data. This pointer can only be used to read the extra data, not change it. After you're done reading the object's extra data, call EndReadExtra to finish the access.
Call BeginModifyExtra to get a pointer to the object's extra data when you want to read or change the extra data. BeginModifyExtra takes one parameter, the object which will have its extra data read or changed. BeginModifyExtra returns an untyped pointer that holds the address of the object's extra data. This pointer can be used to read or change the extra data. After you're done reading and changing the object's extra data, call EndModifyExtra to finish the access.
After you've called the accessor to begin working with the object's extra data, the object is accessed. It is temporarily prevented from being moved by the object runtime until you finish the access. Because objects always occupy contiguous memory, any new memory allocations have to work around the immobile object, which reduces the amount of memory new objects can occupy and hinders the work of the object runtime.
To avoid these allocation problems, you should never perform any action that allocates new objects in memory while an object is accessed. Many common actions, including calling almost any operations, sometimes or always cause memory to be allocated. Because of this severe restriction, you should minimize the period during which the object is accessed by doing as much work as possible before beginning the access. The object runtime provides no other way to access extra data, so you'll have to be aware of these restrictions if your objects use extra data.
The object runtime defines a root list as a collection of well-known objects that can be used by Magic Cap and its packages. There is one system root list, which contains objects defined by Magic Cap itself. Each package has a package root list with elements that are determined by the package.
Root lists are organized as a two-level hierarchy. Most of the elements in the root list are lists themselves. For example, the sixth element in the system root list is a list of the object IDs of all the images available in the Magic Cap ROM. These lists that are elements in the root list are called sublists.
Not every element in the root list is a further list. Some elements are simple, flat objects. For example, the system root list includes an element which is simply the Magic Cap datebook object. CodeWarrior Magic automatically defines symbolic names for many system indexicals at build time.
When you use a system indexical, you're addressing an object in the root list. Similarly, when you use a package indexical, you're addressing an object in the package root list. CodeWarrior Magic defines the MakeIndexical macro to help you create symbolic names for indexicals. The numbers specified when an indexical is defined refer to the object's position in the root list. For example, you can define a package indexical in your C source as follows:
#define ipMrKlaw MakePackageIndexical(26,9)
This defines the symbolic name ipMrKlaw for the 9th entry in the 26th element in the package root list. This definition assumes that the 26th element in the package root list is itself a list object.
You can use indexical symbols in both your C source and in your instance definitions, as in the following examples:
C source:
GoTo(iDatebookScene); PhoneUp(ipMrKlaw); return MemoryImageCommon(iCurrentTask);
Instance definition:
Instance ObjectList 'Receiver' 4; length: 1; entry1: iHallway; // system indexical entry2: ipDevilHouse // package indexical End Instance;
Indexicals that define non-hierarchical objects, like the datebook indexical mentioned earlier, are called flat indexicals. Flat indexicals for packages are defined as follows:
#define iPackageAboutInfo MakePackageFlatIndexical(12)
Note that the distinction between flat indexicals and hierarchical indexicals is only apparent when the indexical is defined. The two kinds of indexicals can be used interchangeably. Indexical names defined by CodeWarrior Magic always begin with the letter i, for indexical. You should use names starting with ip as symbols for your package indexicals.
You can address an entire sublist as an indexical by using the flat indexical defined for that list. For example, the sublist that contains all system-defined windows is defined as follows:
#define iWindowList MakeFlatIndexical(17)
Given this definition, you can address the windows sublist with the iWindowList indexical. In addition, Magic Cap defines symbolic names for the windows themselves, as in the following examples:
#define iToolWindow MakeIndexical(17,8) #define iCollectWindow MakeIndexical(17,9) #define iConfirmationWindow MakeIndexical(17,10) #define iStatusAnnouncements MakeIndexical(17,11) #define iStandardPrinterOptions MakeIndexical(17,12)
Magic Cap defines one indexical, iSystem, to address the system object and the entire system root list, and another indexical, iSoftwarePackage, to address the software package object and the entire package root list. For a complete list of system indexicals, see the file Indexicals.h.
You can address a root list object as an indexical in your instance definition file even if it has no symbolic name defined. To address an object in the system root list, use the numbers of the root list and sublist in braces, as follows :
// start of instance definition here lawnWetness: {39,17}; /* indexical reference to object in system root list */ birdStatus: {40}; /* flat indexical reference to object in system root list */ // rest of instance definition here
In this definition, the lawnWetness field will be set to the object found in the 17th position of the 39th sublist in the system root list. The birdStatus field will be set to the object found in the 40th position of the system root list.
You can also address an item in the package root list as a package indexical without defining a symbolic name for it. To do this, use the numbers of root list and sublist in double braces, as follows:
// start of instance definition here moebiusKind: {{26,2}}; /* indexical reference to object in package root list */ orbitStyle: {{4}}; /* flat indexical reference to object in package root list */ // rest of instance definition here
This definition sets the moebiusKind field to the object found in the second position of the 26th sublist in the package root list. The orbitStyle field will be set to the object found in the fourth position of the package root list.
Although braces are a valid syntax for addressing indexicals in instance definition files, you should always use a symbolic name for indexicals, in the same way that you use variables and constants and avoid hard-coding numbers. You must have a symbolic name to use an indexical in C source - the brace syntax will not pass the compiler.
All positions in the system root list are defined by Magic Cap to hold specific objects. Use the package root list for your own objects. You can effectively replace a system object by changing the value of a particular indexical. For example, position 6,53 (that is, element 53 in sublist 6 in the root list) contains the object ID of a birthday cake image. You could replace this image with your own by substituting your replacement object's ID at the same root list position.
You can use this technique to replace major features of Magic Cap with your own versions. For example, you could replace the calculator or modem server with your own. Many objects are very difficult to replace. Replacing a system object implies fulfilling all the responsibilities of the replaced object.
In package root lists, some positions are defined by Magic Cap, while others are left for your own indexicals. In particular, the first 24 elements in the package root list are defined by Magic Cap. Some of those positions are designed to be filled by your own objects, which you'll specify when you create your package. For example, element 10 is a sublist that contains the user information (help) objects for your package. You can use package root list elements 25 and higher for your own objects.
The system root list is stored as the extra data of the system object, a single object of class System that is present in every Magic Cap implementation. Similarly, the package root list is stored as the extra data of the software package object. There is one software package object for every package in memory.
The system object includes many fields that contain global system information. As with all objects, you should consider the system objects' fields private and you shouldn't access them directly. Instead, class System provides operations to read and change important system values that are stored in its objects' fields. For example, if you want to find out whether Magic Cap is set to use the 24 hour time format, you should call the system object's Display24HourTime operation rather than reading the display24HourTime field directly.
For more information, see these topics in Magic Cap Class and Method Reference:
class Object
operations and attributes:
BeginModifyExtra BeginModifyFields BeginModifyFieldsOf BeginReadExtra BeginReadFields BeginReadFieldsOf Context Copying CopyNear CopyPreferred CopyTransient Destroy DestroyLockedBuffer DestroyTransientBuffer DirectID EndModifyExtra EndModifyFields EndReadExtra EndReadFields Field FieldOf Finalize Implements Import Init MakeStorable MakeUsable NewLockedBuffer NewNear NewPreferred NewTransient NewTransientBuffer PopContext PushReferenceContext ReadFields ReadFieldsOf SetField SetFieldOf WriteFields WriteFieldsOf
macros:
MakePackageIndexical MakePackageFlatIndexical Method
Book Contents Previous Chapter Next Chapter