Custom object added to Group not reflecting that group in C++
by Edward Rotberg · in Torque 3D Professional · 02/24/2010 (3:32 pm) · 22 replies
Hi,
I have a very simple custom object (SceneObject) that I have instantiated in the World Editor. If I move that object to a named Group, and then check its getGroup() group in the debugger, it tells me the name of the SimGroup is "Server Connection". However, when I examine some TSStatic objects that are also in the same World Editor group using getGroup(), the name of the SimGroup is the actual name of the group as it is displayed in the World Editor. I was hoping to be able to determine if my custom object belongs to the same group as these TSStatics but I can't seem to do that.
I have guaranteed that I am looking at the client object for my custom object. None of this is making sense at the moment.
Any ideas? I'm sure I'm missing something simple here.
Thanks in advance,
= Ed =
EDIT: I should add that this is all client-side stuff. I'm dealing with clicking on an object and trying to find out if my custom object is a member of the same group as the object clicked on.
I have a very simple custom object (SceneObject) that I have instantiated in the World Editor. If I move that object to a named Group, and then check its getGroup() group in the debugger, it tells me the name of the SimGroup is "Server Connection". However, when I examine some TSStatic objects that are also in the same World Editor group using getGroup(), the name of the SimGroup is the actual name of the group as it is displayed in the World Editor. I was hoping to be able to determine if my custom object belongs to the same group as these TSStatics but I can't seem to do that.
I have guaranteed that I am looking at the client object for my custom object. None of this is making sense at the moment.
Any ideas? I'm sure I'm missing something simple here.
Thanks in advance,
= Ed =
EDIT: I should add that this is all client-side stuff. I'm dealing with clicking on an object and trying to find out if my custom object is a member of the same group as the object clicked on.
About the author
#2
I'm afraid, however, that this will need to run on a separated server-client setup, so I'm not sure that this will help. Are the TSStatics that I'm looking at server objects then? If not, why are they getting placed in a SimGroup structure? I thought that they were client objects as I'm pretty sure that the raycast involved is happening on the client side - but I could be wrong about that.
There are other ways that we can accomplish what we are trying to do, but none would be as elegant or as easy for our artists and designers to use/understand. I figured they put in this nice organizational feature (SimGroups) for us and I could take advantage of that. I'm sure there are good reasons for not mirroring the SimGroups on the client side, but I'm hazy on what they might be.
Thanks again.
= Ed =
02/24/2010 (8:24 pm)
Thanks Ryan,I'm afraid, however, that this will need to run on a separated server-client setup, so I'm not sure that this will help. Are the TSStatics that I'm looking at server objects then? If not, why are they getting placed in a SimGroup structure? I thought that they were client objects as I'm pretty sure that the raycast involved is happening on the client side - but I could be wrong about that.
There are other ways that we can accomplish what we are trying to do, but none would be as elegant or as easy for our artists and designers to use/understand. I figured they put in this nice organizational feature (SimGroups) for us and I could take advantage of that. I'm sure there are good reasons for not mirroring the SimGroups on the client side, but I'm hazy on what they might be.
Thanks again.
= Ed =
#3
- perform raycast clientside
- send the server the hit object's ghost ID (either through script with "commandToServer" or in C++ using "sendRemoteCommand")
- resolve the ghost ID serverside
- get the server object's group
- loop through the group and see if your custom object is in there
- send the result back to the client
You said this is all clientside stuff, but sounds like you're actually looking at the TSStatics on the server. Are you testing this on a true client?
02/25/2010 (10:18 am)
Well, one way to do it but maybe not optimal would be like this:- perform raycast clientside
- send the server the hit object's ghost ID (either through script with "commandToServer" or in C++ using "sendRemoteCommand")
- resolve the ghost ID serverside
- get the server object's group
- loop through the group and see if your custom object is in there
- send the result back to the client
You said this is all clientside stuff, but sounds like you're actually looking at the TSStatics on the server. Are you testing this on a true client?
#4
I'll be the first to admit that I'm not sure where this is getting executed. I am doing my testing on a stand-alone version, so in my case, I could well be doing this testing on the server objects. That would be unfortunate as this needs to happen on the client, but all of this is initiated from scripts, the the current Canvas (we've made modifications to guiCanvas.cpp). Does this mean the request is sent to the server's guiCanvas??
= Ed =
02/25/2010 (11:02 am)
Ryan,I'll be the first to admit that I'm not sure where this is getting executed. I am doing my testing on a stand-alone version, so in my case, I could well be doing this testing on the server objects. That would be unfortunate as this needs to happen on the client, but all of this is initiated from scripts, the the current Canvas (we've made modifications to guiCanvas.cpp). Does this mean the request is sent to the server's guiCanvas??
= Ed =
#5
The Canvas only exists on the client. If you're doing everything in script, I'm assuming you're using the ConsoleFunction, containerRayCast(). Looking at it's C++ definition, you can see that it does a serverside raycast. So if you're calling this function in response to a mouse event (on the client) it will work in a stand-alone exe for the reasons above (and return a server object!) but will not work on a true client.
One way to separate the server/client stuff:
- in your mouse event script callback, do something like, commandToServer('MyServerRayCast', %start, %end); // clientside
- in function serverCmdMyServerRayCast(%client, %start, %end) // serverside
--- do the containerRayCast();
--- get the group of the hit object
--- loop through the group checking for your custom object's classname
--- if custom object is in group, do commandToClient(%client, 'CustomObjInGroup');
- in clientCmdCustomObjInGroup(), respond accordingly // clientside
02/25/2010 (3:57 pm)
Well, writing server-client code on a stand-alone machine can get tricky since variables like 'gClientContainer' and 'gServerContainer' can be accessed anywhere in code, whether clientside or serverside. Also, methods like Sim::findObject() can "see" both server and client objects. This gives you direct access to server objects in client code and visa versa, which obviously is not valid when you separate the server and client.The Canvas only exists on the client. If you're doing everything in script, I'm assuming you're using the ConsoleFunction, containerRayCast(). Looking at it's C++ definition, you can see that it does a serverside raycast. So if you're calling this function in response to a mouse event (on the client) it will work in a stand-alone exe for the reasons above (and return a server object!) but will not work on a true client.
One way to separate the server/client stuff:
- in your mouse event script callback, do something like, commandToServer('MyServerRayCast', %start, %end); // clientside
- in function serverCmdMyServerRayCast(%client, %start, %end) // serverside
--- do the containerRayCast();
--- get the group of the hit object
--- loop through the group checking for your custom object's classname
--- if custom object is in group, do commandToClient(%client, 'CustomObjInGroup');
- in clientCmdCustomObjInGroup(), respond accordingly // clientside
#6
While I can clearly see why you would want to pass mouse-clicks up to the server in most situations, there are certainly some cases where you want to operate locally and determine yourself when you want to involve the server. Sadly that becomes problematic with Torque at times.
= Ed =
02/25/2010 (5:19 pm)
Thanks Ryan. I will agree with you that having both the server and client sides running in a stand alone environment gets tricky. It's far too awkward to determine where you are running, and in fact at object constructor time, you can't find out which side you are on - at least not with the isClientObject() method.While I can clearly see why you would want to pass mouse-clicks up to the server in most situations, there are certainly some cases where you want to operate locally and determine yourself when you want to involve the server. Sadly that becomes problematic with Torque at times.
= Ed =
#7
For what we are doing, which is non-destructive, and non manipulative of any objects, couldn't we write a clentContainerRayCast ConsoleMethod to do its rayCast client-side by copying the code and it to use gClientContainer instead of gServerContainer? Since we are calling this from our own script anyhow, it would seem to be a simple fix.
I'd love to get your thoughts.
= Ed =
03/09/2010 (8:52 am)
@Ryan -> You said:Quote:The Canvas only exists on the client. If you're doing everything in script, I'm assuming you're using the ConsoleFunction, containerRayCast(). Looking at it's C++ definition, you can see that it does a serverside raycast. So if you're calling this function in response to a mouse event (on the client) it will work in a stand-alone exe for the reasons above (and return a server object!) but will not work on a true client.
For what we are doing, which is non-destructive, and non manipulative of any objects, couldn't we write a clentContainerRayCast ConsoleMethod to do its rayCast client-side by copying the code and it to use gClientContainer instead of gServerContainer? Since we are calling this from our own script anyhow, it would seem to be a simple fix.
I'd love to get your thoughts.
= Ed =
#8
03/09/2010 (3:22 pm)
In the context of the previous discussion, that would definitely be the easiest way to do it, assuming you have a purely client-side method of checking if the hit object "belongs to the same group" as your custom object, whether that be a simgroup or something else. If you don't have a client-side method figured out, I thought of a pretty easy engine change that would automatically place your objects into the proper simgroups on the client when they first ghosted. But I haven't tried it out yet. :)
#9
I've already written the client-side rayCast and it returns the ghost ID just fine. Unfortunately, I can't seem to create an array of ghostIDs for the objects I'm interested in. This is all explained in a new post I've started here:
http://www.torquepowered.com/community/forums/viewthread/112297
If you can explain to me how to get my objects into the proper simGroups on the client-side at ghost time, that would provide an alternative method for me as I can then use that to locate the object hit by the rayCast and more importantly, its "owning" object (explained in the other post).
= Ed =
03/09/2010 (3:30 pm)
Ryan,I've already written the client-side rayCast and it returns the ghost ID just fine. Unfortunately, I can't seem to create an array of ghostIDs for the objects I'm interested in. This is all explained in a new post I've started here:
http://www.torquepowered.com/community/forums/viewthread/112297
If you can explain to me how to get my objects into the proper simGroups on the client-side at ghost time, that would provide an alternative method for me as I can then use that to locate the object hit by the rayCast and more importantly, its "owning" object (explained in the other post).
= Ed =
#10
03/09/2010 (3:46 pm)
Working on it right now, Ed. Gonna see if I can implement it. The basic idea is extending SceneObject by giving it a clientGroup string which is the name of the SimGroup you want it to exist in on the client. When it's ghosted, in its client-side onAdd() method, it checks to see if there is a SimGroup with the name of its clientGroup variable. If it exists, it simply adds itself to the group. If it doesn't exist, it creates the group, and adds itself to it. All subsequent SceneObject-derived objects with the same clientGroup value will find the existing SimGroup upon ghosting and add themselves to it.
#11
Thanks so much!
= Ed =
03/09/2010 (3:55 pm)
Sounds good. Let me know if you finish this and how it works. If you can't get around to it, I'll give it a try myself.Thanks so much!
= Ed =
#12
= Ed =
03/09/2010 (4:14 pm)
Just a thought, but when you create the SimGroup on the client-side, it will probably have to be added as a child of the ServerConnection group, IIRC.= Ed =
#13
I haven't gotten to test this on a seperated client-server. I may try tomorrow. I'll try to post the code here tomorrow as well.
03/09/2010 (8:21 pm)
Hey Ed, I think I got it working. It was actually a little bit trickier than I thought it would be, but not too bad. It's all automatic... whatever SimGroup you place a SceneObject-derived object in on the server, it gets placed in a SimGroup with the same name on the client. If you move the object to a new SimGroup on the server, the client object will automatically get moved to the corresponding SimGroup on the client. If you're on a LocalConnection, however, it will not do this because the client object will get put in the server SimGroup (because of local server-client stuff we've talked about) and you can actually see both the server and client objects in the World Editor tree at the same time. And if you click on the client object, it'll crash. So I set it up to behave this way only for separated client-servers. This way you can do level creation on a LocalConnection machine (or dedicated server), and then when you place it on the dedicated server, it will just work. If you needed to do testing on a LocalConnection machine, it's an easy change, and just be sure not to select the client object in the editor tree.I haven't gotten to test this on a seperated client-server. I may try tomorrow. I'll try to post the code here tomorrow as well.
#14
On a LocalConnection, can't you put the object into it's SimGroup under the server SimGroup? This way I can still test for it on any connection, Local or separate Client. It won't solve the crashing issue in the World Editor, but it might still give me what I need on the client side in just a single version. I can always build a separate World Editor version for that purpose.
= Ed =
03/09/2010 (9:14 pm)
Ryan,On a LocalConnection, can't you put the object into it's SimGroup under the server SimGroup? This way I can still test for it on any connection, Local or separate Client. It won't solve the crashing issue in the World Editor, but it might still give me what I need on the client side in just a single version. I can always build a separate World Editor version for that purpose.
= Ed =
#15
I think I have an easier solution for you. Sounds like you don't really care what SimGroup the ghosts exist in, as long as they're grouped like on the server. So I changed the code to append a "_C" to the client SimGroup name corresponding to the one on the server. So on the server you place objects in TestGroup. On the client they will all exist in TestGroup_C. That gets rid of the client objects showing up in the World Editor and should work for your dedicated server situation. So no special builds, and even better, everything is transparent to your artist.
03/10/2010 (5:19 am)
My ghost creates its SimGroup under the ServerConnection. If the Server object is in TestGroup, then when the ghost tries to see if TestGroup already exists client-side, with a LocalConnection, it actually finds the existing server-side TestGroup (because it can see everything) and places itself in there. That's why you can see it in the World Editor.I think I have an easier solution for you. Sounds like you don't really care what SimGroup the ghosts exist in, as long as they're grouped like on the server. So I changed the code to append a "_C" to the client SimGroup name corresponding to the one on the server. So on the server you place objects in TestGroup. On the client they will all exist in TestGroup_C. That gets rid of the client objects showing up in the World Editor and should work for your dedicated server situation. So no special builds, and even better, everything is transparent to your artist.
#16
In "sceneGraph/sceneObject.h", add a new maskbit to sceneObject:
Add a new variable and some method declarations:
In "sceneGraph/sceneObject.cpp", initialize mClientGroup in the SceneObject constructor:
Add a new protected field in SceneObject::initiPersistFields():
Add a new method, SceneObject::onGroupAdd(), to override the SimObject's virtual method:
03/10/2010 (6:44 am)
Ok, here's the code (based on 1.1 Beta 1)...In "sceneGraph/sceneObject.h", add a new maskbit to sceneObject:
enum SceneObjectMasks
{
InitialUpdateMask = BIT(0),
ScaleMask = BIT(1),
FlagMask = BIT(2),
MountedMask = BIT(3),
ClientGroupMask = BIT(4), // RDM: added mask bit
NextFreeMask = BIT(5) // RDM: shifted next free mask up by one
};Add a new variable and some method declarations:
SimPersistID *mMountPID;
// RDM: start
protected:
StringTableEntry mClientGroup;
static const char* _getClientGroup( void* object, const char* data );
static bool _setClientGroup( void *object, const char *index, const char *data );
public:
const char* getClientGroup() { return mClientGroup; }
void setClientGroup(const char* clientGroup);
void onGroupAdd();
// RDM: end
protected:
/// @name Transform and Collision Members
/// @{
///
Container* mContainer;In "sceneGraph/sceneObject.cpp", initialize mClientGroup in the SceneObject constructor:
mSceneManagerLinks = NULL; mObjectFlags.set( RenderEnabledFlag | SelectionEnabledFlag ); mClientGroup = NULL; // RDM }
Add a new protected field in SceneObject::initiPersistFields():
addProtectedField( "isSelectionEnabled", TypeBool, Offset( mObjectFlags, SceneObject ),
&_setSelectionEnabled, &_getSelectionEnabled,
"Can only be selected in editor if true (and if class is selection-enabled, too)." );
// RDM: start
addProtectedField( "clientGroup", TypeString, Offset( mClientGroup, SceneObject ),
&_setClientGroup, &_getClientGroup,
"The name of the SimGroup the ghost will reside in." );
// RDM: end
endGroup( "Misc" );Add a new method, SceneObject::onGroupAdd(), to override the SimObject's virtual method:
// RDM: start
void SceneObject::onGroupAdd()
{
if( isServerObject() )
{
// Server ojbect changed SimGroups... set mClientGroup to new SimGroup name
mClientGroup = getGroup()->getName();
// Update the client with the new SimGroup name
setMaskBits( ClientGroupMask );
}
else
{
// Client Object changed SimGroups... check to make sure its in the group
// specified by its mClientGroup. This takes care of the first transmission
// at mission loading time. The new ghost will automatically get put
// into ServerConnection, so we catch it here and put it in the correct group.
if( mClientGroup != NULL )
{
if( dStrcmp( mClientGroup, getGroup()->getName() ) )
{
SimGroup* group;
Sim::findObject( mClientGroup, group );
group->addObject( this );
}
}
}
}
// RDM: end
#17
Modify SceneObject::packUpdate to send the new protected field:
Modify SceneObject::unpackUpdate to read in the new protected field and handle adding the group to the client.
03/10/2010 (6:46 am)
Add set/get methods for new protected field:bool SceneObject::_setSelectionEnabled( void *object, const char *index, const char *data )
{
SceneObject* obj = reinterpret_cast< SceneObject* >( object );
obj->setSelectionEnabled( dAtob( data ) );
return false;
}
// RDM: start
//--------------------------------------------------------------------------
// ClientGroup
const char* SceneObject::_getClientGroup( void* object, const char* data )
{
SceneObject* obj = reinterpret_cast< SceneObject* >( object );
return obj->getClientGroup();
}
bool SceneObject::_setClientGroup( void *object, const char *index, const char *data )
{
SceneObject* obj = reinterpret_cast< SceneObject* >( object );
obj->setClientGroup( data );
return false;
}
void SceneObject::setClientGroup( const char* clientGroup )
{
mClientGroup = StringTable->insert( clientGroup );
setMaskBits( ClientGroupMask );
}
// RDM: endModify SceneObject::packUpdate to send the new protected field:
if ( stream->writeFlag( mask & FlagMask ) )
stream->writeRangedU32( (U32)mObjectFlags, 0, getObjectFlagMax() );
// RDM: start
if ( stream->writeFlag( mask & ClientGroupMask ) )
if ( stream->writeFlag( mClientGroup != NULL ) )
stream->writeString( mClientGroup );
// RDM: end
if ( mask & MountedMask )
{Modify SceneObject::unpackUpdate to read in the new protected field and handle adding the group to the client.
// FlagMask
if ( stream->readFlag() )
mObjectFlags = stream->readRangedU32( 0, getObjectFlagMax() );
// RDM: start
// ClientGroupMask
if ( stream->readFlag() )
{
if( stream->readFlag() )
{
char newGroup[256];
stream->readString( newGroup );
// Clientside group will have same name as serverside group + "_C"
dStrcat( newGroup, "_C" );
mClientGroup = StringTable->insert( newGroup );
SimGroup *group;
Sim::findObject( mClientGroup, group );
// If the group doesn't exist clientside, create it and add it to ServerConnection
if( group == NULL )
{
group = new SimGroup();
group->registerObject();
group->assignName( mClientGroup );
SimGroup *serverConn;
Sim::findObject( "ServerConnection", serverConn );
serverConn->addObject( group );
}
// If ghost has already been created, go ahead and place it in its group.
// Otherwise, onGroupAdd() will do this for a newly created ghost.
if( isProperlyAdded() )
group->addObject( this );
}
}
// RDM: end
// MountedMask
if ( stream->readFlag() )
{
#18
Thanks so much for doing this!
= Ed =
03/10/2010 (8:12 am)
Sweeeeet, Ryan! I'll hook this all in today and let you know how it goes. I don't know if I can finish it up this AM before I head out for an offsite meeting, but if not I'll finish up tonight before I head out tomorrow for GDC.Thanks so much for doing this!
= Ed =
#19
Thanks again so very much for this work.
= Ed =
03/10/2010 (8:40 am)
@Ryan: It Works. I had to make some adjustments to your code as it looks like you are running under a different version (1.1?), but it works like a champ! Now I can get back to debugging my own collision detection problems :)Thanks again so very much for this work.
= Ed =
Torque Owner Ryan Mounts
CornerstoneViz
%group = %myClientObj.getServerObject().getGroup();
That should get the group that the TSStatics are in. You could also do a server container raycast instead of a client raycast, which would return your server object directly.
If you are doing this with a separated server-client, you'd have to take a different approach.
*** EDIT ***
Didn't catch that you wanted to do this in C++. The equivalent of above would be: