Data Tables

From HIVE
Revision as of 03:46, 23 November 2010 by DevliN (talk | contribs)
Jump to navigation Jump to search

In StarCraft II, we have a magical container for every single native type. It is called a Data Table, and it is quite like Warcraft 3's hashtable, or a Java Hashmap<String, Object>.

The differences between it and Warcraft 3's hashtable are simple: instead of two integers to save a value, you save it within one string, and in StarCraft II, there are only ever two data tables around at a time. In addition, StarCraft II Data Tables are a lot slower.

The two data tables in use at a time in StarCraft II are the local data table and the global data table.

The difference was not immediately obvious (although you can guess).

Local Data Table

The local data table is a data table that is created for every thread. You get a new local data table whenever a trigger event fires, for example. The data table is cleared when the thread ends. The data table is available inside functions you call. This can be used a way of passing multiple parameters to a function. Just save the argument count into the table and grab them inside the function.

Want to test this? Here are is simple GUI trigger: [galaxy] Untitled Action 001

   Options: Action
   Return Type: (None)
   Parameters
   Grammar Text: Untitled Action 001()
   Hint Text: (None)
   Custom Script Code
   Local Variables
   Actions
       General - If (Conditions) then do (Actions) else do (Actions)
           If
               ("0" value exists in the Local data table) == true
           Then
               Debug - Display ("It exists inside the function! Magi..." + (Text(("0" from the Local data table)))) as debug output using Type 1, and Do display it in the game window
           Else
               Debug - Display "Alack! For it does not exist inside..." as debug output using Type 1, and Do display it in the game window

Untitled Trigger 001

   Events
       Timer - Every 1.0 seconds of Game Time
   Local Variables
   Conditions
   Actions
       Untitled Action 001()
       General - If (Conditions) then do (Actions) else do (Actions)
           If
               ("0" value exists in the Local data table) == true
           Then
               Debug - Display "It exists! Magical." as debug output using Type 1, and Do display it in the game window
           Else
               Debug - Display "Alack! For it does not exist!" as debug output using Type 1, and Do display it in the game window
       Variable - Modify Count: + 1
       Data Table - Save Count as "0" in the Local data table
       Untitled Action 001()
       Debug - Display "--------------------------" as debug output using Type 1, and Do display it in the game 

[/galaxy]

The output is:

[galaxy] Alack! For it does not exist inside the function! Alack! For it does not exist! It exists inside the function! Magical! Its value is: 2


(Note: this is when the trigger fires again after a second)

Alack! For it does not exist inside the function! Alack! For it does not exist! It exists inside the function! Magical! Its value is: 3 [/galaxy]

Other uses than passing parameters to functions include "dynamically sized" arrays that clean themselves up. You cannot permanently save data, however.

Global Data Table

The global data table, as should be obvious when you know what the local data table does, saves data permanently. It is never cleaned out unless you yourself do it. It is created on map init and destroyed when the map ends.

You must manually clean up data if you are no longer using it.

Using Them Safely

If you attempt to use a data table as an array, you should realize a problem very quickly. If you want to have two "arrays", you need to prefix the strings you use to access data in order to not overwrite the other one. This is both annoying and time consuming.

I have made a wrapper class and put in the standard library to deal with this. (Note: geX, you honestly need to allow overloading of functions with the same parameters but different return types. I want to be able to do int func() and bool func() like C# allows me to do. This is kind of a missing feature. Also, being able to use the word "get" as a method name would be awesome.

The reason this tutorial was made, despite the obviousness (well, except for what local data tables do) of the difference between local and global is so I can tell people that they are causing huge memory leaks if they create one of my wrappers as a global table and do not manually delete the elements when they are done with the table. For efficiency, my wrapper class will not be saving a record of everything you put into the table and will not delete the elements you put in when you delete the wrapper.

This wrapper class should be the standard class used for dynamically sized arrays of generic types until geX adds auto boxing/unboxing for wrapper classes, and even then it will likely still have its uses. It will, for example, always be more efficient than an Andromeda Hashmap<String, x>.

a.util.DataTable

Using the wrapper class is simple. Make sure to add an import statement so you actually can USE it.

[galaxy] import a.util.DataTable; [/galaxy]

Anywhere in a function where you want to use a dynamically sized array (heck, you dont even have to specify the size), simply pretend it is a Java ArrayList. Note: The class does not support use of a foreach loop. It does not keep track of entries. A DataTableArray class will be added to deal with this later.

[galaxy] DataTable dt = new DataTable(DataTable.LOCAL); //DataTable.GLOBAL also works. However, it will cause a memory leak unless you manually delete the elements within. Use local in all cases you are not permanently storing an array into a global varaible. for (int i = 0; i < 100; i++) {

   dt.put(i, "this is string " + i); //Note, overloaded methods. put takes parameters like so: (key, value) where key is a string or int, and value is any native type in Galaxy. If you are using a not-quite-int, you should use put<name>(key, int). ie, if using a cinematic, use putCinematic(5, intValueGoesHere);.
   dt.put((string)i, "this is a string! and we overwrote the last thing."); //Note: ints are just casted to strings internally. The keys 5 and "5" refer to the same value. Ints are overloaded specifically so you can pretend it is an array.
   string b = dt.getString(i); //You have to use get<varType>() because Andromeda does not support Objects or overloaded return types (I don't blame Andromeda either for that, but it would be nice to have)
   System.debug(b); //This will cause the game to output "this is a string and we overwrote the last thing." 100 times.

} [/galaxy]

See Also

  • Data Tables and You (Mooglefrooglian, Sc2Mod.com) Original article that this page is based on. Thanks to Mooglefrooglian for the work he put into writing this.