Data Driven Programming Made Easy
Data Driven Programming Made Easy
Made Easy
Eric Malafeew
Harmonix Music Systems
(emalafeew@harmonixmusic.com)
Data-driven programming
• Data drives logic
• Parameterization and scripting
• Benefits
– Faster change - test cycle
– Open program to designers
– More reusable C++
– Enables dynamic programming
Our system
• Evolved over 5 years
• LISP inspiration
• Data format + API + script engine
• Small (70K) but widely used
Python experience
• Just wanted token int
• Got token command string
interpreter function pyint int
• Wrapper hell
• 3 Mb tough on 32 Mb console
Talk topics
• Working with data
• Scripting support
• Advanced integration
• Wrap up
Topic 1: Working with data
Data format
• Basic element: arrays
• Array nodes can be any type
– Subarrays, ints, floats, strings, symbols,
more
• Load from file or create
Example: Data file
(menu
(“bacon and eggs”
(price 12.75)
(calories 120)
)
(“fruit and cereal”
(price 11.75)
(calories 12)
)
)
Anti-example: XML file
<menu>
<item>bacon and eggs
<price>12.75</price>
<calories>120</calories>
</item>
<item>fruit and cereal
<price>11.75</price>
<calories>12</calories>
</item>
</menu>
Anti-example: Raw file
• No structure or annotation
“bacon and eggs”
12.75
120
“fruit and cereal”
11.75
12
Memory representation
DataArray
DataNode*
4-byte value (union of int, float, pointers)
4-byte type
Size
File, Line Packed into 12 bytes
Reference count
• Low overhead
• Serializable
Basic API
// Parse using Flex
DataArray* menu = DataReadFile(“menu.dta”);
// Price
menu->FindArray(“eggs”)->FindFloat(“price”);
price->Release();
Don’t actually use strings
• Mostly use “symbols”
• Unique permanent string
• Saves memory for multiple
instances
• Fast pointer comparisons
• Still need heap strings
Array searches
menu->FindArray(“eggs”)->FindFloat(“price”);
(a (b 1)) // original
(a (b 2) (c 2)) // merge
(a (b 1) (c 2)) // result
Cache files for fast loading
• Avoid text parsing
• Load then serialize into binary file
• Requires special handling of
macros and #ifdef
Program configuration
• Load config file at startup
• Globally accessible
• Encrypt on caching (or you may be
mailed your game cheats)
A default config file
(renderer
(show_timers TRUE)
(screen_size 640 480)
(clear_color 0.3 0.3 0)
)
(mem
(heap (size 30000000) (name bob))
(enable_tracking TRUE)
)
Override in app config file
(renderer
(show_timers FALSE)
)
(mem
(heap (name fred))
)
#merge default.dta
Reloading on-the-fly
• Reload portion of database
• Notify dependent C++
• Must group parameters by user
In-memory param editing
• Interface to cycle and change
params
• Provide extra info for editing
• How to save
(box
(width 2 (range 0 10) (step 1) (desc “Box width”))
(height 3 (range .1 5) (step .1) (desc “Box height”))
)
Topic 2: Scripting support
When you want “code” in data
(launchpad
(run_over ($obj)
{$obj enter_flight (force 10) (auto_align TRUE)}
)
)
2 1. Load level
Editor Game
2. Preview changes
3
1 1 using serialized
Level script protocol
3. Save level
Commands look like
{<func> <args>} or {<object> <method> <args>}
{renderer set_clear_color 1 1 0}
DataRegisterFunc(“+”, Add);
• Returns a node
Implicit arg evaluation
• Node accessors, by default,
automatically execute commands
and provide return value
• Unless accessed as command
• Trades LISP complexity for a little
danger
More on scripting
• “Language” is just built-in funcs
– Avoid stupid names
• Optimization: bind handler to
<func> node on first execute
• Document your script hooks
• Not compiled
Script variables
• Globally named data nodes
• Pointed to by variable nodes
• Automatically evaluate on access,
like commands, by dereferencing
pointer
Access from C++ or script
DataVariable(“game_time”) = 500;
{$focus_character set_speed 5}
{iterate_materials $mat
{$mat set_alpha 0.5}
}
Virtual Handle implementation
{add1 4} // 5
(banana OBJECT
(hit {$this local_hit 10})
)
(berry OBJECT
(hit {$this local_hit 20)})
)
More on DataClass
object->Handle(HitMsg(20));
Specialized C++ handling
HANDLE_MSG(HitMsg)
(object
(HIT {game add_points $points})
)
Balancing C++ and script
• Use script handlers
– For flexibility and prototyping
– To avoid C++ dependencies
– Reduce C++ subclasses
• Use C++ handlers
– Special arg handling
– Performance, maintainance
Topic: Wrap up
Script tasks
• Commands that execute over time
{scheduler delay_task 100 {print “100 ticks later”}}
{scheduler thread_task
{walk_to A}
{wait {near A}}
{walk_to B}
}
More on tasks
• Must preserve variables used in
script from construction time
• Done now explicitly, investigating
LISP closures
{scheduler delay 100 (preserve $msg)
{print $msg}
}
Script debugging
• Dump script call stack on ASSERT
Error: Something’s not right
Script calls:
arena.dta, line 45
game.dta, line 20
• Print statements!
• Interactive debugger next
Conclusion
• Hope this helped to design and use
your data system
• Slides available after GDC at
http://www.harmonixmusic.com/gdc.htm
• Questions?