Language 1: ICE
In
2000, when 3DO (now extinct) was beginning its multi-platform common
library strategy, one of the elements was a scripting language. 3DO was
tired of each project designing and building its own language, then
training the level designers in it.
When our project (Green Rogue
for PS2) started, we, of course, wrote in our design doc that we would
adhere to the new 3DO policy- i.e., that we would use the common
library code, including its scripting language. Then I read the
scripting language documentation. Or rather, tried to read it.
It
seems the new scripting language was really just a macro preprocessor
for C. I struggled to make sense of it and I knew no level designer was
going to read it and comprehend it. The language presumed some event
handling mechanism and timer, but didn’t supply one. That meant each
project would create their own macros and timers and event mechanisms.
Really, each project would be completely unique in its scripting
language with just a minimal common core.
Finally,
all the language did was preprocess C. That meant compiling the script
into the load file for the PS2. If you wanted to change a script, you
had to recompile and redownload the new ELF.
In
those days, the Ethernet to the PS2 DevStation was EXTREMELY slow. The
turnaround for designers when they wanted to change their scripts would
have been horrendous. Which meant we had conflicting needs. On one
hand, sometimes scripts needed to be compiled for maximal speed. On the
other hand, mostly we needed interpreted scripts hotloaded during
gameplay to speed up game production.
Build, Buy, or API?
We
needed a scripting language, as this was for our level designers, not
programmers, so forget API. Various people suggested using existing
off-the-shelf scripting languages, but there were problems associated
with each. All of the existing scripting languages were prepared to be
general purpose programming languages. That meant scripters could try
to write code that could hang our game.
We needed
to tightly control just what things scripters could do and insure no
crash was possible. With complex scripts we knew there was no hope of
ever completely testing all paths of the script. So it was best to
insure it could "do no harm."
Existing scripting
languages also each had their own independent weaknesses. Lua, for
example, had a built-in garbage collector. This is not something you
wanted triggering during a real-time game. Python was strictly
interpreted, which would be too slow for some scripts. And none of
these languages addressed our primary need for compiled AND hotloaded
interpreted. With the continued evolution of these languages, nowadays
I would probably have chosen one of the two, but at that time neither
was appropriate.
So I wrote up a two-page paper
detailing the flaws of the library scripting language and got approval
to build yet another scripting language. It helped that I spent part of
a month building a prototype system to prove the basic concepts of ICE
worked before submitting my request. But my goal went beyond merely
writing a scripting language for our project.
That huge an investment can only be recouped if other projects also
used it. So I intended to completely supplant the existing library
language with my own, which would achieve what 3DO really wanted.
Many
people have written scripting languages for their companies, only to
have the language disappear after zero or one use. To accomplish making
my language truly useful and universal, I needed to do the following:
a) build a better language b) document it so people could learn to use
it c) support it so that any project that needed new features could get
them d) promote it in lunch lectures and by other means so that people
knew why they should want to switch.
Without all
of those steps, it would become yet another scripting language used on
yet another project. And right up there in importance was creating a
great name, something that people would react well to (this is a
marketing exercise). I chose ICE.
ICE combined a
simplified C-like syntax with runtime support for multitasking, events,
timers and interpreted code. All code was “protected” from really dumb
scripter errors. This made it much more than any simple API to common
library code could be and is a classic example where a scripting
language is needed and a bunch of shared library routines you can call
from your programming language just won’t be as valuable.
The
resulting system consisted of a C translator that generated either C
source or interpretable byte-code (depending upon whether you wanted
speed or hotloading).
ICE supported events with
arguments and used those with all asynchronous calls instead of
callbacks. The system supported “listening” for an event with a pattern
match on its two arguments. I greatly prefer this extremely loose
coupling mechanism to callbacks. So when, for example, the game
engine’s animation system finished running some animation on a
character, it would broadcast a completion event naming the animation
and the character. Then ANY code, including the code that initiated the
animation, could react and do things.
One could
write independent routines that did different things with the
information, without ever changing the original code. So special
effects code could be added independently later. This is much better
than virtual functions of class objects, because with those you still
have to have planned a call in advance, knowing what you intend to do.
So if you want to do something more later, you have to go back and
modify existing code.
Because ICE needed
visibility over arbitrary game engine functions, a registration
interface was provided whereby the engine registered the name, code
address, argument description, category, and documentation for routines
being made available to its copy of ICE. ICE also had a command to dump
out the documentation for all routines and engine variables so
registered, so scripters ALWAYS had up-to-date documentation even as
new capabilities were added daily to the game engine’s interface to
ICE.
ICE supported a range of relevant data
types: objects, strings, events, integers, floats, enumerations,
arrays, and records. Two predefined record types: XY and XYZ were built
in, to directly support location and vectors. Type checking was done at
compile time. Objects could be used by name in a script, and as soon as
that name was given an object at run time (perhaps much later in the
game), the object was “registered” and the places in the script that
used that name would be filled in appropriately. So scripts could
easily refer to "Sarge" or "PickupTruck1" and not be concerned about
finding the object involved.
ICE could directly
read simplified C header files. This allowed it to share constant names
and values with the engine instead of having to copy names and values
into the scripting language by hand, a source of potential error that
happened frequently on earlier projects.
ICE became the defacto scripting language for 3DO and the one people were initially required to use never saw real use.
|