Bad Sector
10-10-2006, 02:40 AM
UndeadScript is the scripting language i started writing at some point for my Undead Engine (now Incomplete Undead Engine, since some months ago i started to rewrite everything from scratch but i wanted to keep the "Undead Engine" name and release the source code). It wasn't really completed and i was doing it mostly as an experiment to try making a strong typed scripting language (my previous effort - NerveBreak, which i abandoned at 2005, there is a SourceForge.net project if anyone wants to take over and continue the development - was typeless to the maximum: assign a number to a variable, use it as a string and BOOM).
However it evolved quite nicely. I even used it in Nikwi (http://www.slashstone.com/more/nikwi) to script the monster and item behavior, although the version i used there was very primitive (no function support, for one). These days i needed a scripting language to be around and since i have a "i do everything myself" mentality, i started working on UndeadScript again. I would need to work on it, anyway, once the engine came to a usable state, so the sooner the better because i can use it in other projects aswell (it is not bound to the engine, although it uses the "Undead" part and the "U" prefix in classes).
Up to this point i managed to put proper function support, which -unlike in NerveBreak- don't mess up their variable's data if they call themselves :-), language-aided export support so you can get the address of a function (in order to call it) or variable (in order to read or modify it) from the host program (the game engine), proper floating point and "handle" support (unknown pointer type - can be used for entity handles, etc without exposing their internal structure to the script), array support (a bit limited, though) and the last-and-best: dynamic allocated structures and reference-based garbage collection :-).
The last two means that you can allocate structures defined either by the script or by the host program (currently i haven't wrote the parser part for declaring structures, but it isn't something hard) and allocate memory for them in order to use them.
The garbage collection part means that you don't have to worry about the memory you allocate: once something is not required anymore is dropped and the memory it reserved becomes available again. The "reference based" means that the scripting engine keeps track of where an object (allocated memory for a structure) is used in order to perform garbage collection. For example, in the following code:
Vector v = new Vector;
the created object that results by "new Vector" has a reference count of 0. When assigned to the 'v' variable, it gets a reference count of 1. Extending the code:
Vector v = new Vector;
Vector z = v;
z has the same value as v, the object created by "new Vector" and now that object has a reference count of 2 since it is "referenced" by two variables. But if you extend it to:
Vector v = new Vector;
Vector z = v;
v = new Vector;
Now 'v' has a new object (of the same type) and 'v' and 'z' refer to two different objects. Since 'v' had a reference to another object prior the last line, that other object loses a reference. Now there are two objects and both of them have a reference count of 1. Lastly, if you extend it to:
Vector v = new Vector;
Vector z = v;
v = new Vector;
z = v;
Both 'v' and 'z' refer to the second object and since no variable is referring to the first object anymore, the first object has a reference count of 0 and becomes garbage which will be dropped by the garbage collector.
To be exact, however, an object with reference count of zero doesn't dropped immediately. This is done because it may exist somewhere inside the scripting engine's internal structures or be part of a 'return' statement (used in functions) or for other reasons lose temporary it's references. To deal with this situation, the scripting engine gives a "potentially garbage object" two chances to prove it is not garbage and every 1000 virtual machine instructions, it is checked for 'garbage-ness'. If fails in both chances, then it is dropped. I must note though, that i don't know if this is a "robust" method, but i did many tests and it never failed (in the sense that no object was deleted when it actually WAS needed or garbage object remained in memory). So i can assume that it is a safe bet to say that it 'works'. Of course these were prefabricated tests, i'll know if it truly works once i use it in a real game or program.
For the future, i plan to put true objects and classes in order to make it an object oriented scripting language. It will be very helpful to create classes for -say- monsters where generic monster behavior is scripted in one abstract Monster class and monster-specific behavior is scripted in specific classes derived from that class. And i since i have functions, structure objects and garbage collection, i don't think it will be much of a problem to put classes in there.
But anyway, for the moment, here is a little script showing UndeadScript's syntax (below are the results of running this script):
/*
** UndeadScript math test
*/
Vector originVector()
{
return new Vector;
}
Vector createVector(float x, float y, float z)
{
Vector v = new Vector;
v.x = x;
v.y = y;
v.z = z;
return v;
}
float vectorLength(Vector v)
{
return sqrt(sqr(v.x) + sqr(v.y) + sqr(v.z))
}
float vectorDot(Vector a, Vector b)
{
return a.x*b.x + a.y*b.y + a.z*b.z;
}
Vector normalizedVector(Vector v)
{
float len = vectorLength(v);
return createVector(v.x/len, v.y/len, v.z/len);
}
Vector vectorCross(Vector a, Vector b)
{
return createVector(a.y*b.z - a.z*b.y, a.z*b.x - a.x*b.z,
a.x*b.y - a.y*b.x);
}
dumpVector(createVector(1.0, 2.0, 1.5));
dumpFloat(vectorDot(createVector(1.0, 2.0, 1.5), createVector(1.0, 0.3, 0.4)));
Vector n = normalizedVector(createVector(1.0, 2.0, 1.5));
dumpVector(n);
dumpFloat(vectorLength(n));
Vector z = normalizedVector(vectorCross(createVector(1.0, 0.0, 0.0),
createVector(0.0, 1.0, 0.0)));
dumpVector(z);
(ps. click here for a syntax highlighted version (http://www.slashstone.com/gimme/test1.us.html))
And the results (copy/paste from the console):
badsector@terastios:~/projects/us$ ./usdemo
x=1.000000, y=2.000000, z=1.500000
2.200000
x=0.371391, y=0.742781, z=0.557086
1.000000
x=0.000000, y=0.000000, z=1.000000
badsector@terastios:~/projects/us$
However it evolved quite nicely. I even used it in Nikwi (http://www.slashstone.com/more/nikwi) to script the monster and item behavior, although the version i used there was very primitive (no function support, for one). These days i needed a scripting language to be around and since i have a "i do everything myself" mentality, i started working on UndeadScript again. I would need to work on it, anyway, once the engine came to a usable state, so the sooner the better because i can use it in other projects aswell (it is not bound to the engine, although it uses the "Undead" part and the "U" prefix in classes).
Up to this point i managed to put proper function support, which -unlike in NerveBreak- don't mess up their variable's data if they call themselves :-), language-aided export support so you can get the address of a function (in order to call it) or variable (in order to read or modify it) from the host program (the game engine), proper floating point and "handle" support (unknown pointer type - can be used for entity handles, etc without exposing their internal structure to the script), array support (a bit limited, though) and the last-and-best: dynamic allocated structures and reference-based garbage collection :-).
The last two means that you can allocate structures defined either by the script or by the host program (currently i haven't wrote the parser part for declaring structures, but it isn't something hard) and allocate memory for them in order to use them.
The garbage collection part means that you don't have to worry about the memory you allocate: once something is not required anymore is dropped and the memory it reserved becomes available again. The "reference based" means that the scripting engine keeps track of where an object (allocated memory for a structure) is used in order to perform garbage collection. For example, in the following code:
Vector v = new Vector;
the created object that results by "new Vector" has a reference count of 0. When assigned to the 'v' variable, it gets a reference count of 1. Extending the code:
Vector v = new Vector;
Vector z = v;
z has the same value as v, the object created by "new Vector" and now that object has a reference count of 2 since it is "referenced" by two variables. But if you extend it to:
Vector v = new Vector;
Vector z = v;
v = new Vector;
Now 'v' has a new object (of the same type) and 'v' and 'z' refer to two different objects. Since 'v' had a reference to another object prior the last line, that other object loses a reference. Now there are two objects and both of them have a reference count of 1. Lastly, if you extend it to:
Vector v = new Vector;
Vector z = v;
v = new Vector;
z = v;
Both 'v' and 'z' refer to the second object and since no variable is referring to the first object anymore, the first object has a reference count of 0 and becomes garbage which will be dropped by the garbage collector.
To be exact, however, an object with reference count of zero doesn't dropped immediately. This is done because it may exist somewhere inside the scripting engine's internal structures or be part of a 'return' statement (used in functions) or for other reasons lose temporary it's references. To deal with this situation, the scripting engine gives a "potentially garbage object" two chances to prove it is not garbage and every 1000 virtual machine instructions, it is checked for 'garbage-ness'. If fails in both chances, then it is dropped. I must note though, that i don't know if this is a "robust" method, but i did many tests and it never failed (in the sense that no object was deleted when it actually WAS needed or garbage object remained in memory). So i can assume that it is a safe bet to say that it 'works'. Of course these were prefabricated tests, i'll know if it truly works once i use it in a real game or program.
For the future, i plan to put true objects and classes in order to make it an object oriented scripting language. It will be very helpful to create classes for -say- monsters where generic monster behavior is scripted in one abstract Monster class and monster-specific behavior is scripted in specific classes derived from that class. And i since i have functions, structure objects and garbage collection, i don't think it will be much of a problem to put classes in there.
But anyway, for the moment, here is a little script showing UndeadScript's syntax (below are the results of running this script):
/*
** UndeadScript math test
*/
Vector originVector()
{
return new Vector;
}
Vector createVector(float x, float y, float z)
{
Vector v = new Vector;
v.x = x;
v.y = y;
v.z = z;
return v;
}
float vectorLength(Vector v)
{
return sqrt(sqr(v.x) + sqr(v.y) + sqr(v.z))
}
float vectorDot(Vector a, Vector b)
{
return a.x*b.x + a.y*b.y + a.z*b.z;
}
Vector normalizedVector(Vector v)
{
float len = vectorLength(v);
return createVector(v.x/len, v.y/len, v.z/len);
}
Vector vectorCross(Vector a, Vector b)
{
return createVector(a.y*b.z - a.z*b.y, a.z*b.x - a.x*b.z,
a.x*b.y - a.y*b.x);
}
dumpVector(createVector(1.0, 2.0, 1.5));
dumpFloat(vectorDot(createVector(1.0, 2.0, 1.5), createVector(1.0, 0.3, 0.4)));
Vector n = normalizedVector(createVector(1.0, 2.0, 1.5));
dumpVector(n);
dumpFloat(vectorLength(n));
Vector z = normalizedVector(vectorCross(createVector(1.0, 0.0, 0.0),
createVector(0.0, 1.0, 0.0)));
dumpVector(z);
(ps. click here for a syntax highlighted version (http://www.slashstone.com/gimme/test1.us.html))
And the results (copy/paste from the console):
badsector@terastios:~/projects/us$ ./usdemo
x=1.000000, y=2.000000, z=1.500000
2.200000
x=0.371391, y=0.742781, z=0.557086
1.000000
x=0.000000, y=0.000000, z=1.000000
badsector@terastios:~/projects/us$