Game Development Community

List of Datablocks by Type

by Michael Perry · in Torque Game Engine · 06/11/2007 (7:13 am) · 11 replies

Greetings all!

I need to grab all loaded datablocks by their type (Item, or Player, etc) and store them in a GUI List. I've done some searching already and haven't found an acceptable thread containing the solution.

I was hoping for a simple script solution, but I posted here in case an engine change was required.

I'm aware of the following engine snippets:

Sim::findObject("Armor");  [b]// Finds a specific datablock by name[/b]

// gRootGroup - [b] Contains all datablocks loaded in game[/b]

Anyone know of a thread containing the solution, or have a recommendation? All help is appreciated, and much thanks in advance! =)

#1
06/11/2007 (8:11 am)
I know the editor goes from the rootgroup to load everything for the tree view, but I'm not sure it grabs the datablocks, I think it grabs the objects that've been created...I could be wrong. I've only been working with torque for a month.
#2
06/11/2007 (8:13 am)
Yup. The GuiTreeViewCtrl loads everything based on gRootGroup. The only problem is isolation the specific datablocks, and not using GuiTreeViewCtrl. I want to throw the datablocks and their names in a GuiListCtrl.
#3
06/11/2007 (10:42 am)
Done more looking, but I think the EditorTree which derives from GuiTreeViewCtrl reads the mission file to populate itself, not gRootGroup. It finds objects and gets their fields from gRootGroup based on a selection event.

Rethinking it, my post wasn't very helpful...I was planning on giving you a place to start (model it after the editor), but I've gone over and contradicted myself.

Back to the original question: I don't think very many things are exposed to the script, but it should be pretty easy to do in the C++.

You should be able to call
Sim::getRootGroup
which will return a pointer to the rootgroup.

Maybe this will take you further though:
for (SimSetIterator obj(Sim::getRootGroup()); *obj; ++obj)
{
          ...whatever you want to do with said the obj...
}

Hope this helps.
#4
06/11/2007 (10:49 am)
Well, I was already that far. I'm aware of what exists in the engine, it's just a matter of sorting it and grabbing what I need.

In your example, once I have something in obj, what variable contains the datablock type? How would I check to see if it were a Player type or Item type?

That's where I am currently stumped at, and then I have to think about passing that entire list of strings back to the script side of things.

Fun with the engine, always a good time =)

*EDIT*
......ahem.....
"Wow, Michael that's an interesting question....how would you get the type? Did you ever think of calling getType() on the object and comparing it to an existing typemask...?"

"....i r stupid...."
*EDIT*
#5
06/11/2007 (11:13 am)
Alright, so step 1 has been accomplished:

So, I now have a list to iterate through (thanks for that), and a function to call for checking.

That leaves two steps:
1. Passing in the type to check against
2. Returning a list containing the names of all datablocks of said type.

Passing in the type as a parameter shouldn't be too hard, but determining the type of list to return could be a pain.

I probably only need the name associated with the datablock object, so returning a char* containing space separated strings (names) would allow the creation of a script function to parse out each name and store it in the list.

*EDIT 1*- Which now brings me to string manipulation. Without the handy STL string class which I've become way too dependent on, progression has come to a screeching halt until I figure out a way to put all of the names into a string for returning. So close...more to come...

*EDIT 2*- dStrcat seems like the function to use in this scenario. So now I'll have a master string that I will add to using dStrcat. At the end of the function I'll return the master string and parse that in script...phew...
#6
06/11/2007 (12:38 pm)
Right-O! I'm pretty sure I have the functionality and string manipulation nailed, which has left me with debugging:

ConsoleFunction(getSpecificDatablocks, const char*, 2, 2, "getSpecificDatablocks(typeMask); get all datablocks of \"typeMask\" in string list format")
{
	char* retBuffer = Con::getReturnBuffer(1024);
	retBuffer = "";
	
	for (SimSetIterator itr(Sim::getRootGroup()); *itr; ++itr)
	{
                // Get our object
		SimObject* obj = static_cast<SimObject*>(*itr);
		
                // Get the object name
		const char* name = obj->getName();

                if(!name)
                      continue; // No sense crashing the compiler or adding nameless datablocks
		

                // What datablock type are we looking for
		U32 searchType = dAtoi(argv[1]);

                // What is the object's type
		U32 objType = obj->getType();
		
                // Compare types
		if(searchType & objType)
		{
			[b]dStrcat(retBuffer, name);[/b]
			Con::printf("Object of set found");
		}
	}
	return retBuffer;
}

So, I have no compile errors, crashes, or warnings. However, the code I've put in bold shows the bug I'm dealing with. No matter what I pass in, I never make it through the last logic check. I know I've had this problem before.

I've tried passing in all of the following, which do exist in the mission:

$TypeMasks::InteriorObjectType
$TypeMasks::TerrainObjectType
$TypeMasks::ShapeBaseObjectType
$TypeMasks::PlayerObjectType
$TypeMasks::CorpseObjectType

Any clues?

*EDIT*- Bit manipulation and logic error strikes again! The == needed to change to & for the check to work properly, since the mTypeMask of some objects are not just the descriptive type, but a combination of multiple masks =).

The new bold section shows a crash that occurs for dStrcat(), so...onward I go!

If you see any glaring errors for string manipulation, or know why this might be crashing, please let me know
#7
06/11/2007 (1:11 pm)
I think I figure out problems faster if I just keep making mistakes, talking them out on the thread, and then posting results =).

No more crashes, just one bug. Here's the new code:

ConsoleFunction(getSpecificDatablocks, const char*, 2, 2, "getSpecificDatablocks(typeMask); get all datablocks of \"typeMask\" in string list format")
{
	char* retBuffer = Con::getReturnBuffer(1024);
	dStrcpy(retBuffer, "");
	
	for (SimSetIterator itr(Sim::getRootGroup()); *itr; ++itr)
	{
		SimObject* obj = static_cast<SimObject*>(*itr);
		
		const char* name = obj->getName();
		if(name)
		{
			if(!dStrcmp(name, "Terrain"))
				int breakHere = 1;
		}
		else
			continue;
		
		U32 searchType = dAtoi(argv[1]);
		U32 objType = obj->getType();
		
		if(searchType & objType)
		{
			dStrcat(retBuffer, name);
			Con::printf("Object of set found");
		}
	}
	return retBuffer;
}

I'm too used to Torquescript. I was setting a pointer (retBuffer) to a bad value (""), instead of performing a dStrcpy.

So, I am now grabbing objects of a specific type from all loaded objects in a game! Hurray!

The last bug I can find is that objects are being added twice, which I assume is due to ghosting. So, I'm going to fix this quirk then move on to script functionality...getting close now...
#8
06/11/2007 (8:40 pm)
So, I found a much better solution for iterating through datablocks and getting all the info I need:

ConsoleFunction(getSpecificDatablocks, const char*, 2, 2, "getSpecificDatablocks(typeMask); get all datablocks of \"typeMask\" in string list format")
{
	// Create a return buffer (string format)
	char* retBuffer = Con::getReturnBuffer(1024);
	
	// Clear out existing data in the return buffer
	dStrcpy(retBuffer, "");

	// Get the typemaks we are searching for
	const char* searchType = argv[1];

	// Get all of our datablocks into one variable, a datablock group
	SimDataBlockGroup * grp = Sim::getDataBlockGroup();

	// Traverse the datablock group
	for(SimDataBlockGroup::iterator i = grp->begin(); i != grp->end(); i++)
    {
		// Grab our current iterator's datablock
		SimDataBlock * datablock = dynamic_cast<SimDataBlock*>(*i);

		// Skip non-datablocks if we somehow encounter them.
		if(!datablock)
			continue;

		// Get datablock name for matching
		const char* name = datablock->getNamespace()->mName;

		// Safety check, just in case
		if(!name)
			continue;

		// Match datablock types
		if(!dStrcmp(searchType, name))
		{
			dStrcat(retBuffer, datablock->getName());
			dStrcat(retBuffer, " ");
		}

	}
	return retBuffer;
}

Works beautifully, not to mention it is a lot cleaner and requires less iteration. I'll post the script implementation tomorrow.
#9
06/12/2007 (7:15 am)
After a good night's rest, I was able to find the rest of the bugs with the code. Here is the updated version, with an explanation of bug-fixing at the end:

static const char* findNamespace(SimDataBlock * datablock, const char* searchValue)
{
	// Grab the namespace
	Namespace* pTemp = datablock->getNamespace();

	// Traverse the family tree looking for our
	// long lost parent
	while(pTemp->mParent)
	{
		// Compare our names...are you my Mommy?
		if(!dStrcmp(searchValue, pTemp->mName))
		{
			// We have found the parent we are looking for, get their name
			return pTemp->mName;
		}
		// This isn't the parent you are looking for, go higher in the tree
		pTemp = pTemp->mParent;
	}
	// We didn't find the parent, return 0 (poor, orphaned datablock (= )
	return 0;
}
ConsoleFunction(getSpecificDatablocks, const char*, 2, 2, "getSpecificDatablocks(typeMask); get all datablocks of \"typeMask\" in string list format")
{
	U32 iBufferSize = 0;
	Vector<const char*> myVec;

	// Get the typemaks we are searching for
	const char* searchType = argv[1];

	// Get all of our datablocks into one variable, a datablock group
	SimDataBlockGroup * grp = Sim::getDataBlockGroup();

	// Traverse the datablock group
	for(SimDataBlockGroup::iterator i = grp->begin(); i != grp->end(); i++)
    {
		// Grab our current iterator's datablock
		SimDataBlock * datablock = dynamic_cast<SimDataBlock*>(*i);

		// Skip non-datablocks if we somehow encounter them.
		if(!datablock)
			continue;

		// Get datablock name for matching
		const char* name = findNamespace(datablock, searchType);

		// Safety check, just in case
		if(!name)
			continue;

		// Match datablock types
		if(!dStrcmp(searchType, name))
		{
			// Keep track of our overall memory we are going
			// to allocate for our return buffer
			iBufferSize += dStrlen(datablock->getName())+ 1;

			// Push the string back
			myVec.push_back(datablock->getName());
		}
	}

	// Create a return buffer (string format)
	char* retBuffer = Con::getReturnBuffer(iBufferSize);

	// Clear out the return buffer (paranoia check)
	dStrcpy(retBuffer, "");

	// Iterate our vector and fill up our returnBuffer
	for(int i = 0; i < myVec.size(); i++)
	{
		dStrcat(retBuffer, myVec[i]);
		dStrcat(retBuffer," ");
	}
	
	// Return
	return retBuffer;
}

I thought the functionality was working beautifully, except I was only checking AudioDescription, AudioProfile, and ParticleData. When I tested PlayerData, ParticleEmitterData, and SplashData, the search failed.

What I forgot (very stupid of me), is that there is a namespace hierarchy. As an example, AudioDescription derives from SimDatablock. So, AudioDescription->NameSpace->name will always be found in the datablock variable I'm using, since it is an immediate child of the datablock base class.

After debugging with the "PlayerData" parameter, I was able to find the actual string containing "PlayerData" was located in datablock->namespace->parent->parent->name.

The end result was creating a helper function that would traverse the "family tree" of a datablock looking for our search parameter (such as "PlayerData"). If I found the value I was looking for, I continued on with the rest of the functionality. If the search term was not located in the "family tree", I returned 0 (null string), forcing the SimDataBlockGroup::iterator to skip to the next iteration of the loop.

With that in place, the function was finally working EXACTLY how it was intended. When I began testing for datablocks that have been instantiated many times, I tried using more memory than I allocated for the returnBuffer. The last step was to create a more dynamic method of storing the strings. I ended up using Vector, and keeping track of the memory I was going to allocate with each successful iteration. Once the datablock looping was complete, I allocated the memory for the returnBuffer and dumped the contents of the Vector in. Yay completion!

Script implementation:
[b]// Try passing in "PlayerData", "AudioProfile", and others[/b]
function getDatablocks(%name)
{
[b]   // Get the list of datablocks[/b]
   %list = getSpecificDatablocks(%name);

[b]   // How many did we find?[/b]
   %count = getWordCount(%list);
   
   echo("Number of " @ %name @ " datablocks: " @ %count);
   
[b]   // Loop through the list, grab each datablock, and print it out to the console[/b]
   for(%i = 0; %i < %count; %i++)
   {
      %name = getWord(%list, %i);
      echo(%name);
   }
}

PHEW! I love a good challenge! =)

*EDIT*- The final, working code is now up. Let me know if you find bugs
#10
06/12/2007 (8:46 am)
LOL, sorry I wasn't as helpful as I'd hoped :) Congratulations!
#11
06/12/2007 (9:02 am)
Any help provided was appreciated Aaron =).

I've submitted the resource, which is now pending: List Datablocks by Type