Game Development Community

Best method for storing groups of objects/data?

by Dennis Harrington · in Torque Game Builder · 07/18/2006 (11:43 am) · 8 replies

Since I started using TGB earlier this year, one of the things I've struggled with is finding the "best" way of keeping track of a group of objects and/or data. It seems to me that TS is somewhat limited in this regard. I mean to say that while there is no shortage of storage methods, they all seem to be lacking in one way or another.

So, I've come to the conclusion that using a SimSet or SimGroup is the "best" and most flexible method for storing objects. BUT, since it's still not as flexible as I would like I just wanted to see if there are any other techniques out there that I've overlooked.

An example may help to understand where my concerns lie. Say I have a CCG type of game like Magic and I want to create and keep track of each player's deck of cards. So I'm dynamically creating card sprites and adding each one to a SimSet or SimGroup that becomes each player's "deck".

Now I want to deal out cards from this virtual deck but I want to be sure they're in (and stay in) a very specific order. Or maybe at some point I want to allow the user to manually re-order them. One of the things that's caused me problems is the apparent lack of a "hard" ordering of objects in a SimSet. Is there anyway to enforce a specific order in a SimSet? If I add objects to a SimSet in a specific order can I be 100 percent sure that they will always be in this order until and unless I change it?

In my current project, the ordering has been consistent so far but since I've had issues in previous projects with objects not staying in order I just wanted to try to understand why and how the ordering could be lost. Is it something I'm doing wrong or is it just the way that the SimSet is contructed?

I hope this makes some sense. ;)

#1
07/18/2006 (12:10 pm)
I don't think you can gaurantee an order with SimSets. In Ruby's Eggcellent Adventure (a Bomb Jack clone) I had to keep the eggs in order so I knew which one to highlight next. I forced it by assigning an extra field to each egg that was its number.

I just looped through the SimSet until I came to the right number. It didn't then didn't matter which order the SimSet was actually in, although in testing random deletions from the SimSet, they never actually got out of order. But I figured better safe than sorry :)

If you wanted to insert a card into the middle of the deck, although the actual SimSet object would put that card at the end, by assigning it a number and shuffling the others along one, you could get your cards in the right order.
#2
07/18/2006 (7:15 pm)
GPGT says that SimSets work like a traditional queue. The order should be stable. The objects will be in whatever order you put them in. You can also push objects to the back, etc.
#3
07/19/2006 (5:17 am)
Yes, normally the order of objects in a SimSet is the order you added them. If you remove an object from the middle, the order of the surrounding objects stays the same and the indeces shift, etc.

That said, it's possible to implement different data structures using TorqueScript without the need for SimSets. For example, I made a fully functional script-based inverse kinematics chain class (essentially a doubly-linked list) with normal SceneObjects, and I just recently made a finite state machine class that just requires one ScriptObject per FSM by some good ol' exploitation of TS array indexing.

It's all a matter of taking the basic computer science concepts you know and applying them to a different environment. Once you know the in's and out's, TS is a really dirty thing. There are all sorts of extremely useful black-magic things you can do with TS that really make it beautiful to work with (like skipping between namespaces, or any of the 1001 disgusting+amazing things you can do with eval).

ScriptObject is more useful than anything else, as far as creating custom data structures via script. It's really the most bare-bones object you would want to deal with (none of the t2dSceneObject overhead) and it's still got all the things you need it to - most importantly, an Object ID so you can reference it.

I would suggest designing a basic deck ScriptObject that stored a doubly-linked list structure for cards that make up a deck. So you would have a top pointer and a bottom pointer and you could traverse back and forth. Then you could deal and stack cards from/onto either end of the deck easily. Then your cards could also be ScriptObjects that stored basic information about that card and it's relationship to the "game" and maybe the name of an image or whatever you need.

SimGroups are much more dangerous and should really (and I mean REALLY) be avoided unless you know what you're doing. The main reason for this is that any given object can only be a member of one SimGroup at any given time and it's really easy to accidentally bump objects out of groups that they need to be in to maintain stability just by adding them to another group.

I hope this was helpful. If you need any help post back and I would be happy to give you a hand.
#4
07/19/2006 (5:29 am)
Although Simsets will retian order if you simply add items then remove in a back to front order, they will not reatin ordering if you remove an item that is not the last item added to the set.

Personally I'd avoid using a simset class for anything that requires ordering, since you can never guarantee that the implementation of the set is going to remain the same and since sets do not by their very defination have an order (even though the current implementation does), who knows what optimisations could be done in the future that break ordering.(Unlikely event I know, but is it worth the headaches later?)

A better option imo would be to create your own SimOrdered class using simset as a reference and simply fix up the removal code so that it retains ordering via a safe delete rather than bumping the last entry into the new vacent spot. It doesn't take much time to copy the simset code, change it to SimOrdered and fix up the remove function.

That way you retain the extra efficiency of a set when its possible but have an ordered set for the occaisonal time you need that too. Also if the simset functionality changes your ordered class will still be ok.
#5
07/19/2006 (11:25 am)
Thanks for all of the great replies. This is a lot of helpful information and it's exactly the kind of feedback I was hoping to receive.

I think another reason I was using SimSets was due to the fact that I could put the actual object in each entry instead of just an ID as would be the case in an array. But it sounds like the ScriptObject solution may be my best bet. I'll have to experiment with it a bit and find the best way to implement it.

@Thomas: I would be curious to hear a bit more about your FSM implementation as I've been reading 'Game AI by Example' lately and I've been trying to determine a nice way to implement an FSM structure in TS.

Thanks again for the feedback, everyone!
#6
07/19/2006 (5:21 pm)
My FSM is just a class ("t2dFSM") with a few methods defined that's supposed to be used as a superclass for script objects.


You create an FSM like so:
// define your script object
new ScriptObject(MyFSM)
{
   class = "MyFSMClass";
   superclass ="t2dFSM"; 
};

// register the states
MyFSM.registerState("A");
MyFSM.registerState("B");
MyFSM.registerState("C");

// set the initial state
MyFSM.setState("A");

Each state must have at least a filter function defined for it, or the FSM won't register it. Filter functions should return whatever state the machine is supposed to switch to (or return nothing).
function MyFSMClass::filterState_A(%this)
{
   if([some condition])
      return "B";
}

function MyFSMClass::filterState_B(%this)
{
   if([some condition])
      return "C";
}

function MyFSMClass::filterState_C(%this)
{
   if([some condition])
      return "A";
}

The FSM will also check for enter and exit functions for each state and call them during transitions if they exist...
function MyFSMClass::enterState_A(%this)
{
   echo("entering state: A");
}

function MyFSMClass::exitState_A(%this)
{
   echo("leaving state: A");
}

Thats all you have to do to use it. When you want the FSM to check for transitions, just call MyFSM.checkState().

Here is the code that makes it work:
function t2dFSM::hasState(%this, %stateName)
{
   return %this.stateLookup[%stateName] !$= "";
}

function t2dFSM::registerState(%this, %newStateName)
{
   if(!%this.hasState(%newStateName) && %this.isMethod("filterState_"@%newStateName))
   {
      %this.stateLookup[%newStateName] = "_";
      return true;
   }
   
   return false;
}

function t2dFSM::removeState(%this, %stateName)
{
   if(%this.hasState(%stateName))
   {
      %this.stateLookup[%stateName] = "";
      return true;
   }
   
   return false;
}

function t2dFSM::forceState(%this, %stateName)
{
   %this.currentState = %stateName;
}

function t2dFSM::setState(%this, %stateName)
{
   if(%stateName !$= %this.currentState && %this.hasState(%stateName))
   {
      if(%this.isMethod("exitState_"@%this.currentState))
         eval(%this@".exitState_"@%this.currentState@"();");
         
      %this.currentState = %stateName;
      
      if(%this.isMethod("enterState_"@%this.currentState))
         eval(%this@".enterState_"@%this.currentState@"();");
   }
}

function t2dFSM::checkState(%this)
{
   if(%this.hasState(%this.currentState))
   {
      if(%this.isMethod("filterState_"@%this.currentState))
      {
         %nextState = eval(%this@".filterState_"@%this.currentState@"();");
         
         if(%nextState !$= "")
            %this.setState(%nextState);            
      }
      else
      {
         %this.removeState(%this.currentState);
      }
   }
}
#7
07/20/2006 (11:45 pm)
In the next version a SimSet function called "reorderChild" is exposed to script, this function is identical to the one attached to GuiControl and even in this case it simply calls the reorder function on the SimSet :)
#8
07/22/2006 (1:06 am)
@Thomas: That's awesome. Thanks for posting that. It'll really help me get my FSM going on my current game and I'm sure others will appreciate it as well. Very simple and effective. Thanks!

@Matt: That's great news. That'll most definitely come in very handy. ;)