Object Runtime


What do all those bits in an ObjectID mean, anyway?


Being able to tell something about an object from its ObjectID is an incredibly handy thing to be able to do; here's a description of the more important bits in an ObjectID:

Given an 32 bit value 0xXYZ00000, you can tell it's an ObjectID or indexical if the high (leftmost) bit is set. In other words, an ObjectID or indexical will always start with a digit from 0x8 through 0xF. The digit represented by X tells you the logical cluster that that object is in. For example, an ObjectID starting with 0x8 tells you that the object is in the system persistent cluster; a 0xB tells you that the object is in the package persistent cluster.

Digit Y gives you more information about the type of ObjectID this is. If it's odd (0x3 or 0x7), that means this ID is a system indexical number. If it's even, that means this ID refers to an object instance. The next bit of this digit is know as the the indirect bit. If this bit is set (Y's value is 0x2 or 0x6), this means that this ID is a reference object. Reference objects are used to refer to objects that did not originate from your context. You'll see instances of these objects when you Import objects from another package. Note that the indirect bit is set for indexicals. The next higher bit is the usable bit. If this bit is set (0x4), it means that the object can be accessed (or usually, is already accessed). When you're executing a class operation and look at the ObjectID for the self parameter, this bit will always be set. This bit is clear if the object is not being used at the time; if you look at some random ObjectList in the system, the ObjectIDs it contains will (should!) all have this bit clear.

Finally, digit Z mostly contains generation bits. If an ObjectID ever gets reused, these bits will start getting set to distinct this instantiation of the object number from an older one.

The rest of the numbers in an ObjectID comprise the actual object number. This is used as an index into a MasterBlock in a cluster. A MasterBlock entry gives an offset into the physical cluster where the object is located. As objects move around in memory, its MasterBlock entry is updated, but the object number stays the same. This is similar to how handles work on the Macintosh.

For a complete description of the bits in an ObjectID, look in the file {MagicDeveloper}Interfaces:FrameworkDefines.asm.h.


What is the +1 that sometimes follow the length of an object in a cluster dump?


The physical size of an object may not be the same as the logical size of an object (to maintain alignment in memory for speed). The +1 is the adjustment.


Is there any way to specify data is private to an object, so it is only accessible to the object itself?


All the fields (data) of an object are accessible to other objects, if its objectID and class definition are known.

However, we consider it "bad form" to access fields of classes other than your own. They should be treated as private, and future tools may provide enforcement for this practice.


My package has an intrinsic function which takes an ObjectID and a Pointer as its parameters. Sometimes, on calling this function, the application goes into the debugger with the error message "Using storable ID". What causes this to happen?


An ObjectID has two formats, its storable form and its usable form. When you look at an ObjectID when it is held in a field of an object, it must be in its storable form. An ObjectID will almost always be in its usable format when it is in a local variable on the stack, and it must be in the usable format when it is passed as a parameter to another method.

You can tell at a glance which form any given ObjectID is in by looking at its second nibble, going from left to right. If bit 2 (going from right to left) in this nibble is set, then the ObjectID is in its usable form. If this bit is clear, the ObjectID is in its storable form. (For example, 0x84nnnnnnn is a system object in its usable form; 0x80nnnnnn is a system object in its storable form.) When an ObjectID is about to be stored in an object, it will be converted into its storable form. Magic Cap will return the usable form of an ObjectID from attributes and operations; when an operation is entered, all ObjectID parameters will be in usable form. The debugger message you see means that this conversion was not performed on an ObjectID; you are trying to "use" an ObjectID that is in its storable form.

The conversion between storable and usable forms of an ObjectID is handled automatically when you call a getter or setter to get or store a field whose type is an object. This conversion is also done automatically when you call FieldOf() or SetFieldOf() on an object field. This conversion is not done when you use ReadFields(), WriteFields(), BeginRead() or BeginModify(). If you use any of these calls, you must call MakeUsable() when you pull an object out of a field, or MakeStorable() when you store an object into a field. This conversion is also not done if the type of the field is Unsigned instead of an object. If you are storing an ObjectID into an unsigned field, you should either make the field an object type, or call MakeUsable() and MakeStorable() explicitly when you access that the contents of that field. Additionally, if you keep ObjectIDs in the extra data portion of your objects, you need to call MakeUsable() on any ObjectID you get out of extra data, and MakeStorable() on an ObjectID before you store it into extra data. Of course, once you've made an ObjectID storable, you'll need to call MakeUsable() on it before you can access any of its fields or pass it along to another operation.

You may ask, "Well, if all that's different between an ObjectID being usable and storable is setting that one bit, why bother?" The catch is that much more can happen during the conversion process. For example, if you are getting an ObjectID out of an imported object, Magic Cap will create a Reference object that refers to the object that you want so that the dispatcher can switch into the correct context when it calls on operation on this object. Similarly, if you store an object from your package context into an imported object, Magic Cap creates a Reference object and stores the ObjectID for the Reference object.

Because any object access (a read or a write) can potentially create a Reference object, the normal rule about not being able to allocate memory when objects are locked down apply. This means that you really shouldn't be accessing ObjectIDs within BeginRead()/EndRead() and BeginModify()/EndModify() sections of code. In the future, there might be tools to catch you if you make this mistake, but for the present, you need to avoid these situations on your own. If you find that your code accesses objects within these sections of code but everything seems to work, you should move the object accesses out of that section; just because the code works on your machine doesn't mean it will work on your customers'.


Is there any way to statically instantiate instances of a class that lives in another package? I don't want to have to create these objects at runtime.


Unfortunately, this is the way that the runtime works and there's no way around it. The problem is that because package class numbers are unique only within the context of that class, there's no way to specify an instance of a foreign class. The secondary problem is that because of the first problem, imports must be described as a package context and the native class number, but the ObjectID of the package context isn't guaranteed to stay the same across clean ups.


In a mixin class, can I override an operation that is defined by the class that the mixin mixes in with?


Yes, this is a perfectly valid thing to do. Several mixins defined by Magic Cap override operations. An interesting aspect of overriding operations from mixins is the order in which the inherited operations get called. Say the following class hierarchy exists:

Define Class A;
  inherits from Object;

  operation BaseOperation();
End Class;

Define Class B;
  inherits from Class A;

  overrides BaseOperation;
End Class;

Define Class C;
  mixes in with B;

  overrides BaseOperation;
End Class;

Define Class D;
  inherits from Class B;
  inherits from Class C;

  overrides BaseOperation;
End Class;
What happens when D_BaseOperation calls InheritedBaseOperation()? Actually, it's pretty straightforward. Magic Cap always follows the mixin inheritance chain before following the primary inheritance chain. In this example, the inheritance path would be D-C-B-A. We can even make this more twisted, by throwing in a subclass of the mixin class C, and change the inheritance of class D to inherit from this new class:
Define Class E;
  mixes in with B;
  inherits from C;

  overrides BaseOperation;
End Class;

Define Class D;
  inherits from Class B;
  inherits from Class E;

  overrides BaseOperation;
End Class;
Following the rule about mixin inheritance before primary inheritance, the execution path of InheritedBaseOperation() calls would be D-E-C-B-A.


I have a package that receive creates telecards from information coming from an external server. There's a lot of information on each card, so the Telecard objects can be rather large: up to 50K or so. If the package where the Telecards are created (either my package or the new items package) gets to be larger than 500K, and is packed up, the communicator resets every time I try to unpack the package. What can I do to solve this problem?


This is a side effect of the Magic Cap shadowing and cleaning mechanisms. Whenever an object is changed, Magic Cap creates a new copy of the object in transient memory, in the uncommitted changes cluster for that package. This is done to protect the consistency of objects stored in persistent memory. Periodically, Magic Cap saves the objects in the uncommitted changes clusters into persistent memory.

When a package is packed up, any references to objects outside of that package context are set to nilObject. When a package containing cards is packed up, the stack field of all cards that refer to stacks not in the package are set to nilObject as part of this cleaning process. When the package is unpacked, the stack fields are reset as part of installing the cards into the stacks. Because this represents a change in the object, Magic Cap shadows the object into the uncommitted changes cluster.

In this case, the amount of card data (500K) you have exceeds the amount of transient memory that is available. Magic Cap will run out of memory while trying to unpack the package, causing the reset.

The core of this problem is that each individual telecard is large, so shadowing the card will consume a large proportion of transient memory. You need to reduce the amount of space the card will take up in the uncommitted changes cluster. This means you should consider not using card extra data to store the information you receive from the server. One possibility is to store the data in text fields which are subviews of the cards created by your package. The text fields (or more specifically, the data store for the text fields) will not get shadowed when the package is unpacked, so this reduces the amount of memory consumed by the shadows of the card objects.