How to create superclasses and subclasses.
Objects can inherit methods and properties from other classes/objects.
As stated previously, this is useful for the example Parrot object
that can inherit methods and properties from the soon-to-be-created
"Bird" class, which in turn could inherit from an "Animal" class, etc.
Each class provides a way of providing common methods and
properties for a group of like individuals.
A "superclass" is a class (or object) with methods or properties
that are inherited by other classes. A "subclass" is a class
that inherits from a superclass.
Creating a superclass
To create the "Bird" superclass:
-
Create an object/class called "Bird", using the
same general actions as those used to create "Parrot".
-
Unlike Parrot, the "Automatically create as an object",
found under "Super-classes" in
the "Modify object" page, should not be checked.
(It could be checked, but then you'd end up with both a Parrot
object and a Bird object.)
-
As before, add the "FlightSpeed" public property to
the Bird class, and type in an initial value, like 10.
-
Add the "Fly" public method to the Bird class and provide
that outputs a Trace("This is Fly from the bird class") so you
can tell that it was called.
That's it; you've created a bird class.
(Of course, this is a very simple example so you can understand
the process.)
Using a superclass
To get the Parrot object to use the Bird class:
-
Switch to the "Modify object" page for the Parrot
object, and press the "Super-classes" tab if
it isn't already visible.
-
Press the "Add class" button.
The "Add class(es)" page will appear. It lists all of the
classes in the project.
-
Check the "Bird" class.
-
Return to the "Modify object" page for the
Parrot object. You'll see that "Bird" is now listed as
one of the superclasses.
If you compiled now though, the Parrot class would be unchanged
because it already supports the "FlightSpeed" property and "Fly"
method. Because of inheritance rules, if a sub-class (aka: the
Parrot object) supports a method or property from a super-class (aka:
the Bird class) then the sub-class's properties or methods
have priority.
Therefore, you need to remove both the "FlightSpeed" property
and "Fly" method from the Parrot object (not the Bird class).
-
Switch to the Properties tab and change the
property drop-down for "FlightSpeed" to "blank", effectively
deleting the property.
-
Switch to the Methods tab, hold
the control key down, and click on
the "Fly" method button. This will delete the
method.
Since Parrot no longer supports the properties and methods
from the Bird class, they will be inherited directly.
-
To test your changes, compile and
run the Test compiled code. The FlightSpeed
property will be the value initialized in the Bird class,
and the Fly method will call the code from the Bird class.
Why use superclass?
So what? That didn't really do much. Now that you have a
Bird class that defined flying, here's how you can use it:
-
Override a property's inheritance - If you add the
"FlightSpeed" property back into the Parrot class but use
a new initial value, you can make Parrots fly slower or
faster than the "typical" bird.
-
Override a method's inheritance - If you add the
"Fly" method back into the Parrot class with different code
you can make parrot's fly differentlu than "typical" birds.
-
Other subclasses - Since most types of birds (swans,
ducks, hawks, etc.) are really just small variations on the
basic "bird" concept, you can quickly create many different
types of birds (sub-classes) based on the "Bird" super-class.
Where the type of bird (swan, duck, etc.) varies from the
super-class "Bird", you provide new properties and/or methods.
-
Multiple inheritance - As stated in the last
tutorial, you can have the Parrot object inherit not only
from the Bird class, but also from a "TalkingCreature" class.
Parrots, humans, aliens, and even robots would be
members of the "TalkingCreature" class, sharing properties
and code specific to creature that talk.
I won't lead you through all these examples step-by-step
because they're all variations on a theme.
Superclass order
If an object is a sub-class to two or more super-classes
you may need to prioritize the super-classes so that any
overrides (for properties or methods) are correct. Such
situations will arise if two or more superclasses support
the same properties or methods.
The "Super-classes" tab in the "Modify object"
lets you control the priority of all the object's
superclasses.
Method inheritance
Earlier I said that if a class (the Parrot object) provides
code for a method (the "Fly" method),
any code from the superclass (the "Fly" method in the Bird class)
will be ignored.
I lied. This isn't always the case.
Some method definitions allow the methods to coexist.
You can have a call
to a method first call the method in the lowest sub-class (the
Parrot), then the next sub-class (the Bird class), and the
next one (the Animal class), etc. You can reverse the order
and call the methods from the highest super-class first, down
to the lowest sub-class. You can also allow this chain to
be stopped based on the returns of the methods. This creates
four possibilities, in addition to the "just override inheritance"
that's the default.
You might want to do this to accomplish:
Cumulative effects - If you wanted to allow the
bird with an egg inside it to fly slower, because it weighs more,
you could temporarily decrease the "FlightSpeed" property and
then try and remember to increase it when the egg was laid.
Alternatively, you could create a "FlightSpeedGet" method that
just did the following:
return (parameter[0] = FlightSpeed);
|
This would return the bird's flying speed, and would be
the "official" way of getting the value; you could even make
the property private.
The variable, parameter, is automatically defined to be the
list of parameters passed into the method. Since the
FlightSpeedGet() call takes no parameters, the list will
initially be empty. Setting paramter[0], however, changes this.
Normally, this would have no effect... however:
You could also create a super-class called "Pregnant". It would
support the FlightSpeedGet() method too, but its code would be:
return (parameter[0] *= 0.75);
|
If you had the FlightSpeedGet() method call the super-class
first, parameter[0] would be set to the original flight
speed. Once that returned, the next class down (Pregnant) would
be called; this would retrieve the value "stashed away" in
parameter[0], decrease it to account for the egg's extra weight,
and return that.
Thus, to make a pregnant Parrot you'd include the Pregnant
class as a super-class. You could also add and remove the
pregnancy later using layers (discussed later).
This idea could be extended further by making an "Unhealthy"
super-class that likewise reduces flying speed. If a Parrot
is both Pregnant and Unhealthy it will fly even slower.
Sometimes capture, sometimes pass it on - Alternatively,
you could tell the method definition to call all the methods
from sub-to-super-class or super-to-sub-class, repeating until
one of them comes up knows how to deal with the situation.
Perhaps you might need a public method, LikeFood (FoodName), that returns
true if the object (aka: Parrot or Bird) likes a food item (passed
in as the parameter "FoodName") or false if it doesn't.
The generic bird would return true for (FoodName == "birdseed") ||
(FoodName == "peanut"), but
false for (FoodName == "flower").
return (FoodName == "birdseed") || (FoodName == "peanut");
|
Some parrots like flowers, in addition to to birdseed. You could
provide have the LikeFood() method override, cut-and-paste the
code from the Bird's LikeFood() method, and then modify it.
Or, you could have specify (in the method definition) that it
should call from the highest priority methods (the Parrot's)
to the lowest priority methods (the Bird's), trying
LikeFood() for each one. If any returns a value which is not
undefined, then that return is used. Otherwise, the next in
line is called.
The Parrot's LikeFood() method would then be:
if (FoodName == "flower")
&tab;return true;
else
&tab;return undefined; // pass it on
|
This makes for a very elegant solution, especially if new
foods are added later on.
Both the "cumulative effects" and "sometimes capture, sometimes
pass on" approaches to methods are supported by
MIFL. To have a method definition use either of these:
-
Switch to the "Misc." tab in the "Modify
method definition" page for the method you wish to
change.
-
Change the "Overrides" setting to the method
you wish to use. The default is "Call only the highest
priority method", but the other four possibilities are
also listed.
-
Make sure to document this behavior so that
anyone using that method definition will know what to expect.
Recommended properties and methods
Realistically, the flying speed for every bird is different,
and while a few birds will fly at the FlightSpeed (property)
as initialized by the Bird superclass, most birds will fly
at different speeds.
To make sure that anyone creating a new type of bird will
override the default FlightSpeed property for the bird, or
consciously decide not to override it, you can can add
a setting to the "Bird" class so that if any other class
is derived from it (such as a "Robin"), an entry for
the FlightSpeed property will automaically be added to the
Robin class's list of public properties.
To do this:
-
Switch to the "Misc." tab in the "Modify object"
page for the "Bird" class.
-
Press the "Recommend properties and methods for
sub-classes" button.
The "Recommended" page will appear, displaying the properties
and methods that are automatically added to any sub-classes
created from the Bird class.
-
Check the "FlightSpeed" property.
Once the "FlightSpeed" property is checked, anytime the Bird
class is included as a super-class of another object (or class),
the "FlightSpeed" property will automatically be added to
the object's list of public properties.
Objects – Layers
Describes what layers are and how to use them.
MIFL includes an object concept called "layers".
They allow an program to add new super-classes to an object
at run-time. They also allow new methods and property get/set
calls to be added at run-time.
The primary use for layers is to allow objects to dynamically
change their beahaviors (methods) and properties in an
easy-to-program way. As an example from the last tutorial,
layers could be used to add a "Pregnant" or "Unhealthy" superclass
to a "Parrot" object. The layer allows all the methods
associated with being Pregnant or Unhealthy to be added
at run-time, and removed when the state no longer applied.
To see how this works, first create a superclass that can be layered.
-
Create an "Unhealthy" super-class.
-
Add the public method, "Fly", to the Unhealthy super-class.
The code should be different than before:
Trace ("An unhealthy bird can't fly.");
return false;
|
That's all you need to test out the concept. You'll know
if the unhealthy superclass has been added to the Parrot
object because calling Fly() will output different text.
To test the Unhealthy superclass:
-
Compile the project and
then run the Test compile code menu item.
-
Type "Parrot.Fly()" and press "Run this".
From the debug trace you'll see that the Fly() code for the Parrot
comes from the Bird class. The parrot is still healthy.
-
To add the Unhealthy class to the parrot object,
type "Parrot.LayerAdd ("SickLayer", "Unhealthy", 1);" and
press "Run this".
The "SickLayer" parameter is a name that you given to the layer
so you can later idenfity which layer it is. (After all, you might
have several layers.) The "Unhealthy" parameter is the name of the
class. And 1 is the priority of the class; higher numbers have
higher priority over other classes. The class used to create
an object always has priorty 0, and is usually the lowest
priority.
-
Now, type "Parrot.Fly()" and press "Run this".
The Fly() code from the Unhealthy class will be run instead.
The debug trace will indicate that the parrot is too sick to fly.
-
You can also see the layer by using the right column
of the test display. Click on "Objects...",
then "Parrot", and finally, hover the mouse
over the "Layers" entry.
-
To remove the layer,
type "Parrot.LayerRemove (0);" and
press "Run this".
The 0 parameter is the index into the layer. If you used 1
instead, the "Parrot" class would be removed from the object,
making it an unhealthy nothing. Under normal circumstances
you'd look through all the layers in an object to find
the one named "SickLayer" and then delete that one. This
is just a tutorial though.
There are quite a few layer methods you can call. Some
of them are:
-
LayerAdd - Adds a new layer to an object.
-
LayerGet - Gets information about a layer
-
LayerNumber - Returns the number of layers in an object.
-
LayerRemove - Removes a layer from an object.
Adding individual methods
You can also add individual methods to a layer (although
adding classes (aka: groups of methods) is recommended).
To add an individual method:
-
In the "Test compiled code" page,
type "Parrot.LayerMethodAdd (0, "Speak", Trace)" and
then press "Run this".
The 0 parameter causes the method to be added to the layer
at index location 0. "Speak" is the name that the new method
will use. And, Trace, is the function (or method) to call when
"Speak" is called.
At the moment, you cannot compile code on-the-fly, so the
only code you can add is that which has already been compiled,
either in a function, this object, or another. (I can add
the ability to code on-the-fly, if necessary.)
-
Because "Speak" is not a compiled token, you can't
just type "Parrot.Speak ()". However, you can call
the method using, "Parrot.MethodCall ("Speak", "Polly wants
a cracker." and pressing "Run this".
The "Speak" parameter is the name of the method to call; it
could also be "Fly", or other methods supported by the Parrot.
"Polly wants a cracker." is the parameter passed into the "Speak"
method, which just ends up calling Trace().
Therefore, you should see "Polly wants a cracker." appear
on your debug trace.
Layers and properties
When you include a super-class in your object, it automatically
inherits alls the properties from the superclass.
This is not the case for layers. If you add
a layer with a new class, the method definitions are incorporated
into the object, but properties are not.
MIFL specifically does not add properties when a layer is added
since doing so might unpredictably (for the programmer) cause
hundreds of properties to suddenly change.
Of course, there's an easy work around for this: Just set the
properties immediately before or after the layer. Or, provide
a function in the layered class that initialzes any necessary
properties.
One aspect of the properties is inherited when you layer on
a super-class though. Properties can include code that is called
whenever the property is accessed, through a get() or set().
Some properties need to trap access calls so they can verify the
value they're being changed to is acceptable.
If a layered super-class includes code for get() and set() of
its properties, this get() and set() code will be incorporated
into the object while the layer exists.
Furthermore, just as there's a way to add individual methods
to a layer, there is also a way of adding get() and set() code
for properties to a layer. This won't be discussed in the
tutorial though.
Objects – Containership
Tutorial about how one object can contain another.
MIFL objects understand containership. They can contain other
objects and be contained by an object. This is an extremely
useful tool for interactive fiction, which has all sorts
of containers, like rooms containing objects, characters
containing inventory objects, and backpacks containing
objects.
To have an object initially created within another object:
-
First create an object that can be a container. Using
everything you've learned so far, create
a "Birdcage" object. It doesn't need any properties
or methods. Make sure it has the "Automatically
create as an object" option checked.
-
Modify the Parrot object so it starts out contained within
the birdcage. Bring up the Parrot's "Modify object" page
and switch to the "Super-classes" tab.
-
Underneath the "Automatically create as an object" checkbox
is a drop-down listbox. Open it up and select "Birdcage" from
the listbox.
-
Compile the code and go to Test
compiled code .
-
You can use the right column to see the containership;
click on "Objects...", then "Birdcage",
and hover the mouse over the "Contains" item.
A popup will appear showing that the birdcage contains
the parrot.
-
If you switch to the Parrot object you'll see
that it is contained by the birdcage.
That's all you need to do.
You can change containership at run-time using the following
methods:
Objects – Timers
Tutorial about the use of timers.
In MIFL, all timer events are associated with objects.
This means that to enumerate all the timers, you need to enumerate each
object and discover what times it supports. It also
causes times to be deleted when the object is deleted, or
saved when the object is saved.
Creating a timer is easy:
-
Switch to the Test compiled code window.
-
Type "Parrot.TimerAdd ("SpeakTimer", true,
1.0, Trace, "Polly wants a cracker.") and
press "Run this".
The "SpeakTimer" is a name you can use to identify the
timer. True indicates that the timer keeps repeating; if you
passed in false it would only call itself once and then stop.
1.0 is the number of seconds before the timer goes off.
Trace is the function (or method) to call, and
"Polly wants a cracker." is the parameter to pass into the
function (or method).
In other words, this timer causes the parrot to speak,
"Polly wants a cracker." over and over again.
-
To see the effects, press the "Refresh" button
in the "Debug trace" section.
-
You can also see the timer by clicking, "Timers (active)..." in
the right-hand table.
There are three ways to stop the timer:
-
Run "Parrot.TimerRemove ("SpeakTimer")". This
deletes the timer.
-
Run "Parrot.TimerSuspendedSet (0.0)". This
suspends all of the timers in the parrot object.
You can reactivate the timers by calling this function and
passing false in as the parameter.
-
Delete the parrot by running "delete Parrot".
That's the basics. Here's a complete list of timer methods:
-
TimerAdd - Adds a new timer to the object.
-
TimerEnum - Enumerates all the timers in an object.
-
TimerQuery - Returns information about a timer.
-
TimerRemove - Deletes a timer from an object.
-
TimerSuspendedGet - Returns TRUE if the object's
timers are suspended.
-
TimerSuspendedSet - Suspends or resumes an
object's timers.
String tables and resources
Tutorial about the use of timers.
MIFL includes two special entities, string tables and resources.
The are both extremely useful for writing an application.
String table
The "String table" is a list of strings with
a name attached to each. For example: One entry in the string
table might be "Hello world!" with the name, SHelloWorld (the S
prefix indicating it's a string).
When you have entered a string into the string table, you
can refer to the string by it's name. Therefore,
calling Trace(SHelloWorld); will print out "Hello world!".
The advantages of this follow:
-
Centralized - If someone needs to change a string
that's displayed to the user, they can look for it in one (centralized)
location, instead of scanning through all the code.
-
Multiple references - If a string is used over and over
again, you only need to keep one copy of it in the string table.
This not only saves memory, but it makes it easy to change
all occurances of the string at once.
-
Faster code - A reference to a string table (SHelloWorld)
is stored internally as a number, making it very fast to pass
around. The string table reference is only converted to
a string, "Hello world!", at the last moment.
-
Localization - This is the most important aspect of
the string table. If a program only references SHelloWorld then
translating the program to spanish only requires that
the string table for SHelloWorld be changed from "Hello world!"
to "Hola el mundo!".
Here's the really cool part... the string table can contain the
string for both "Hello world!" and "Hola el mundo!". The version
it uses depends upon the user's language. This allows your application
to be written with English, Spanish, Japanese, etc. strings already
translated. If someone in an English speaking country runs it,
your program will use English. If they run it from Japan (or otherwise
indicate they're Japanese), they get Japanese from the program.
Furthermore, if MIFL is used for an online interactive fiction server, then
users in the US will see English prompts, and users from
Japan will see Japanese prompts, even though it's the same
program running. (Their chat text won't be translated though.)
Before creating a string (in the string table), you should
first identify what languages your project will be
translated into:
-
Select the "Project settings" menu item
under the "Misc. menu.
In the "Project settings" page that appears, you'll find
a section for "Languages". This lists all the languages
supported by your project.
-
To add a new language, select the language
underneath "Add language" and then
press "Add language". You will see the
language added in the list to the right.
To add a string to the string table, just:
-
Select the "Add new string" menu item
under the "Misc. menu.
The "Modify string" page will appear.
-
Type in the "Name", such as "SHelloWorld".
-
Type in the string for each of the languages.
You might end up leaving some languages blank because you
don't know them and are relying on a translator.
If a language's string is blank when the program is run,
the first non-blank string will be used, even though it
isn't for the right language.
-
Revisit any code you wrote and replace all occurances
of "Hello world!" with SHelloWorld.
-
Compile your code and use the Test
compiled code page to test that the strings still
display.
Even if you added Japanese translation of "Hello world!", when
your HelloWorld() function was called, it probably printed
out "Hello world!". This is because it thinks you're in
an English speaking area. (Although theoretically if you're
in Japan it will automatically default to Japanese, and instead,
refuse to display the English version.)
To tell MIFL what language to use:
-
In the "Test compiled code" page,
type "LanguageSet(1041)" and
press "Run this".
The 1041 parameter is an ID for Japanese. English is 1033.
The language IDs conform to the standard LANGID #defines in
winnt.h. If you don't have this but wish to use the translations,
please contact me.
-
Calling HelloWorld() will now say "Hello world!" in
Japanese, assuming that you had entered a Japanese translation
for the string.
-
To find out what language MIFL is using
type "LanguageGet()" and
press "Run this". The return value is the
current language ID.
Warning: - Using a string table with multiple
languages can introduce some difficult-to-find bugs. For
example, look at this code:
MyGlobal = SHelloWorld;
Trace (MyGlobal);
|
This will write "Hello world!" to the debug trace if the
language is set to English, "Hola el mundo!" for Spanish, etc.
No problems here. Lets complicate matters:
MyGlobal = SHelloWorld + " " + SHelloWorld;
Trace (MyGlobal);
|
This will write "Hello world! Hello world!" to the debug trace if the
language is set to English, "Hola el mundo! Hola el mundo!" for Spanish, etc.
No problems here. Now for the problem:
LanguageSet (1033);
MyGlobal = SHelloWorld + " " + SHelloWorld;
LanguageSet (1034);
Compare = SHelloWorld + " " + SHelloWorld;
Trace (MyGlobal == Compare);
|
The trace output is false. One might expect
it to be true since the exact same sequence of operations
is used to produce MyGlobal as well as Compare. Why is this?
"MyGlobal = SHelloWorld + " " + SHelloWorld;" ends up converting
SHelloWorld into a string. Since the current language is English,
the English version of SHelloWorld will be used from the string table.
MyGlobal ends up with the string "Hello world! Hello world!".
However, "LanguageSet (1034);" changes the language to
Spanish. When "Compare = SHelloWorld + " " + SHelloWorld;" is called,
the Spanish versions of the string are used.
Compare ends up being "Hola el mundo! Hola el mundo!".
The two strings are compared (as strings). To the program
they're obviously not the same.
Resources
Resources are desgigned to hold data that isn't
a string and isn't code. This could include images,
sounds, etc.
Unforuntately, the type of resources a MIFL
project contains depends upon its context; different types
of resources will be available for interactive fiction than
for 3D modelling. Since this tutorial is written to be
context independent, it won't be very specific about the
types resources.
To create a resource:
-
Select the "Add new resource" menu item
under the "Misc." menu.
-
A new page, "Add a resource", will appear. Click on one
of the listed resource types. Unfortunately, I can't
be detailed here.
When you click on a resource, a new page will appear, "Modify
resource". It lets you change the name of the resource as well
adding resources for specific languages.
-
Type in a "Name", such as "RMyResource". The
"R" prefix indictes it's a resource.
-
Type in a "Description" so that when you see
the list of resources you'll know what it's for.
-
Press "Add" for one of the languages. This
will bring up a dialog that's specific to modifying the
resource you have selected.
If you have specified multiple languages for your project,
you'll notice that the resource can contain slots for each
of the languages. This lets the resource automatically
adjust to the user's language, just as string tables did.
If this were an audio resource with a recording of someone speaking,
you could include several recordings, one for each
supported language.
And, just as with string tables, if you only fill in one
resource entry, that one will be used, despite the user's
choice of lanugages. You probably don't need localized versions
of ever image, for example, although signs and other letters
within the images might cause a problem.
Resource usage is context dependent, so I can't show that
here. As a general rule, the content (such as interactive fiction)
will provide a library specific to the context. It will
contain functions for using the resources the context supports.
Constructors and destructors
Describes how constructors and destructors work in MIFL.
MIFL supprots constructors and destructors for objects.
A constructor is called when an object is created, and
a destructor is called when an object is deleted (usually).
Classes and objects typically use constructors
to initialize variables and start timers. Destructors are usually
to unravel relationships the object has with other objects.
The use of a constructor and/or destructor for an object
is option. To use one, the object needs to support
the public method Constructor() and/or
the public method Destructor().
It may also want to support Constructor2().
Constructor() and Desctructor() are called under the following
circumstances:
-
When a program is first run, all of the automatically
created objects are created. Then, Constructor() is
called for all the objects.
-
If a running program has been saved to disk, and is reloaded,
the Constructor2() method will be called for all
the loaded objects instead of Constructor().
-
When an object is created using
"new", Constructor() will be called for the object.
-
When an object is deleted using
"delete", Destructor() will be called for the object.
The destructor will be called before all the timers
have been deleted.
-
If a program is shut down even though objects are still
around, then Destructor() will not be called.
Datatype overview
Describes the different data types.
MIFL supports the following data types:
Boolean
A boolean can either be true or false.
Both true and false are keywords built into the language.
Convert data-type to boolean |
Data-type | Conversion |
Boolean |
NA |
Character |
if '\0' then false, else true |
Function |
true |
List |
true |
List.Method |
true |
Null |
false |
Number |
if 0 then false, else true |
Method |
true |
Object |
true |
Object.Method |
true |
Resource |
true |
String |
if "" then false, else true |
String.Method |
true |
String table |
if "" then false, else true |
Undefined |
false |
Character
A character is a unicode character, from 0 to 65535.
Characters are specific by using single-quotes around a single
character, such as 'x' or '?'.
The same escape sequences that strings use can appear in
a character, such as '\n'.
Convert data-type to character |
Data-type | Conversion |
Boolean |
't' if true, 'f' if false |
Character |
NA |
Function |
|
List |
'\0' |
List.Method |
'\0' |
Null |
'\0' |
Number |
convert to Unicode character |
Method |
'\0' |
Object |
'\0' |
Object.Method |
'\0' |
Resource |
'\0' |
String |
first letter of the string |
String.Method |
'\0' |
String table |
first letter of the string |
Undefined |
'\0' |
Function
This is a reference to a function. Function names are
tokens generated at compile time.
Convert data-type to function |
Data-type | Conversion |
Boolean |
undefined |
Character |
undefined |
Function |
NA |
List |
undefined |
List.Method |
undefined |
Null |
undefined |
Number |
undefined |
Method |
undefined |
Object |
undefined |
Object.Method |
undefined |
Resource |
undefined |
String |
searches through the function list for the name |
String.Method |
undefined |
String table |
searches through the function list for the name |
Undefined |
undefined |
List
A list is an array of values (which can be any of the datatypes,
including other lists). For more information on lists, see
the tutorial about them.
Convert data-type to list |
No conversions possible. |
List.Method
A "List.Method" is a combination of a list and a method assocaited
with that list. Although this is a dataype, programmers won't
usually think of it as one since it's an intermediate step
towards accessing the list's methods, such as List.ListAdd();
Convert data-type to list |
No conversions possible. |
Null
The null type has no values.
Convert data-type to list |
No conversions possible. |
Number
A number is a floating point value (in C/C++ terms it's
an 8-byte double) that has 15 digits of precision and ranges
from (approx) -1e300 to 1e300.
A number can be written in the following forms:
Convert data-type to number |
Data-type | Conversion |
Boolean |
0 if false, 1 if true |
Character |
Unciode value of the character |
Function |
0 |
List |
0 |
List.Method |
0 |
Null |
0 |
Number |
NA |
Method |
0 |
Object |
0 |
Object.Method |
0 |
Resource |
0 |
String |
convert the first characters of the string to a number |
String.Method |
0 |
String table |
convert the first characters of the string to a number |
Undefined |
0 |
Method
This is a reference to a method. Method names are
tokens generated at compile time.
Convert data-type to method |
Data-type | Conversion |
Boolean |
undefined |
Character |
undefined |
Function |
undefined |
List |
undefined |
List.Method |
keep only the method |
Null |
undefined |
Number |
undefined |
Method |
NA |
Object |
undefined |
Object.Method |
keep only the method |
Resource |
undefined |
String |
searches through the public method list for the name |
String.Method |
keep only the method |
String table |
searches through the public method list for the name |
Undefined |
undefined |
Object
This is a reference to an object.
Convert data-type to list |
No conversions possible. |
Object.Method
An "ObjectMethod" is a combination of an object and a method assocaited
with that list. Although this is a dataype, programmers won't
usually think of it as one since it's an intermediate step
towards accessing the object's methods, such as Object.MyMethod();
It is a useful construct for timers and other callbacks since
it remembers both the method to call and object to which it
belongs.
Convert data-type to object.method |
No conversions possible. |
Resource
A resource is a reference to a resource (in the project).
Resource names are generated at compile time.
Convert data-type to resource |
No conversions possible. |
String
A string is a reference to memory containing a Unicode string.
For more information on strings see the tutorial.
Convert data-type to string |
Data-type | Conversion |
Boolean |
"false" if false, "true" if true |
Character |
string with the one character |
Function |
function name |
List |
list elements, separated by commas |
List.Method |
"list." with the method name appended |
Null |
null |
Number |
decimal form, or an exponential form if it's too large |
Method |
method name |
Object |
the return from Object.Name(), or the object's class |
Object.Method |
the return from Object.Name(), or the object's class,
followed by "." and the method's name |
Resource |
convert to string |
String |
NA |
String.Method |
"string." followed by the method's name |
String table |
the string designated for the current language |
Undefined |
"" |
String.Method
A "String.Method" is a combination of a string and a method associated
with that string. Although this is a dataype, programmers won't
usually think of it as one since it's an intermediate step
towards accessing the string's methods, such as String.StringSlice();
Convert data-type to string.method |
No conversions possible. |
String table
A string table is a reference to a string table (in the project).
String table names are generated at compile time.
Convert data-type to string table |
No conversions possible. |
Undefined
The undefined type has no values.
Convert data-type to undefined |
No conversions possible. |
Get() and Set() code
Describes how to intercept get() and set() calls to global variables and properties.
Typically, when a global variable or an object's property is
created, the variable's contents can be accessed directly.
Any function or method can change the variable (or method)
to any value.
You may wish to protect variables (or methods) so that only
valid values can be written. Alternatively, you might
need the accessing of a variable to run some code. (For example:
The "weight" property of a backback might sum of the weight
of its contents and its own weight.)
You can do such interceptions by providing "get and set" code
for the variable or property. There are two ways to do this:
- Check the "Use get/set code" in the variable's or property's definition
- Use some run-time calls to change the get/set code for variables or properties
"Use get/set code" checkbox
Providing your own get/set code for a global variable or
an object's property is easy. If you are modifying a global
variable you will find a checkbox for "Use get/set code" and
one button each for "Get" and "Set. If you are modifying an
object's properties, you'll find a similar checkbox and buttons.
To provide your own get/set code:
-
Check the "Use get/set code" checkbox.
-
Press the "Get" button to modify the code to
get the variable.
-
Press the "Set" button to modify the code to
set the variable.
Note: When you attach get/set code to a variable or method,
any reads from or writes to the variable will access the
get/set code. Except, from within the code for handling get
and set itself; this will be able to access the variable directly.
Run-time calls
Modifying the get/set code for a global variable or
object's property at run-time is slight more difficult.
To modify a global variable's get/set code you need
to call GlobalGetSet(). See the function's definition
for more information.
To modify an object's property's get/set code you need to
call LayerPropGetSetAdd(). See the
method's definition for more information.
Load/save objects
Describes how to loading/saving objects from/to disk works.
MIFL lets you save one or more (or all) objects to disk, and
then reload them at a later point. The exact function needed
to do this depends upon the context, so I can't go into
details here.
However, I can explain the basic process:
Saving
When one or more objects are saved, the following steps are taken
for each object:
-
Identify the initial properties in a virgin object - A "virgin"
form of the object is created using the object's class (from the
lowest-priority layer). The virgin object has its properties
initialzed according to the "initialized to" values for
the object's methods.
-
Save only property changes - The to-be-saved
object is compared to its "virgin" version. Only those properties
that have been changed (or deleted) will be saved.
This is a very useful feature since it not only reduces the
space necessary to save objects, it makes it safer for authors
to save all the objects, change some properties, and then
reload the previous objects (which were created with old code).
Since only changed (or deleted) properties were saved, the
realoded objects will contain all the properties as prescribed
by the new code, except for the changes.
Changing the code between a save and load isn't 100% safe
though, and problems can arise. For example: If a saved
object relied on the class "Unhealthy", but when it reloaded
discovered that no "Unhealthy" class exists, it will ignore
the "Unhealthy" class.
-
Save all layers - All the layers of the object
are saved, including any single-instance methods and get/set
code.
-
Save contained in - The object reference of the
object's container will be saved. The contained objects'
references are not, however, because this information
can be reconstructed from the containers.
-
Save all timers - All the object's timers are
saved. The timer's suspended state is also saved.
In addition, if all the objects are saved, the following
information is also saved:
-
Global variable changes - Just as only changed
(and deleted) properties are saved, so too with global variables.
Any unchanged global variable is ignored.
A note about numerical accuracy: - Because of the
wave data save is currently implimented, numbers may be rounded
off to the nearest 0.00001. This is not a problem for most
numbers, but might cause problems under some circumstances.
Loading
If all the objects were saved, the following
information is loaded:
-
Global variable changes - All the global variables
are created and set the the "initialized to" value from compile.
Then, any changes are loaded from the saved version.
If any globals were deleted prior to saving then they're
deleted.
When the saved objects are reloaded, the following steps happen
for every object:
-
Check for object ID conflict - If there is a conflict
in the object's ID with an object that already exists, then
a new ID is created. The object's ID is changed to the new ID,
and any saved object (or data) that references the old ID gets
updated.
-
Create layers - The loaded object has all the
proper layers setup.
-
Create properties - All the properties (from the
lowest priority layer) are added using the compiled
"initialized to" values.
-
Load property changes - Any changes to the
properties are loaded. This also includes property deletions.
-
Load contained in - The contained-in reference
is loaded and the loaded object is placed in the container.
If the container no longer exists then the loaded object
is not placed in any container.
-
Load all timers - All the object's timers are
load. The timer's suspended state is also loaded.
-
Call Constructor2 - If the object supports
a Constructor2() method, that is called so the object knows
it has been loaded from a saved state.
Loop and stack limits
Talks about limits to loops and stack depth.
MIFL performs the following checks to prevent infinite loops:
-
Loop counts - For most contexts, MIFL will
abort a loop if it is more than a million iterations.
This protects against infinite loops.
-
Call-stack depth - For most contexts, MIFL will
abort a function or method call if it is more than 1000
function/method calls deep. This protects against infinite
recursion.
Share with your friends: |