Ghosting Limits
by Jesse Allen · in Torque 3D Professional · 09/08/2014 (12:47 am) · 212 replies
Greetings fellow Torque-goers! I hadn't been overly active on the boards lately, but that's usually a good thing since it means I'm busy actually developing stuff :) Finally hit a snag today, and I was hoping someone a bit more knowledgeable about these things could perhaps point me in the right direction.
I've been working with generating 1000's of cubes, which I have been largely successful with. I have scripted algorithms to do exactly what I need as far as generation is concerned. I've even managed to work out some basic culling and so on. The problem I'm having, however, is with the following error:
NetConnection::object In Scope: too many ghosts.
I did a bit of searching and I did stumble on Vince Gee's fantastic resources for Limiting Shapebase ghosting and Improved Limiting ghosting . These do appear to be useful by their own rights and I will definitely explore these options. However, these particular resources deal specifically with ghost limiting as related to view distance.
The problem I am having seems to stem from the actual hard limits set on the maximum number of allowed ghosts. I was curious:
Also, bonus points for a clear explanation on how view distance affects ghosts in the first place. If I generate in a bunch of cubes and use the command ServerConnection.getGhostsActive() I can see the current number of ghosts. Check. But if I then reduce the viewDistance to an absurdly low amount (such as 10) and run far away from the generated cubes...the command will still show the same number of ghosts, regardless of my distance from them.
Cheers guys, as always thanks in advance for any help.
I've been working with generating 1000's of cubes, which I have been largely successful with. I have scripted algorithms to do exactly what I need as far as generation is concerned. I've even managed to work out some basic culling and so on. The problem I'm having, however, is with the following error:
NetConnection::object In Scope: too many ghosts.
I did a bit of searching and I did stumble on Vince Gee's fantastic resources for Limiting Shapebase ghosting and Improved Limiting ghosting . These do appear to be useful by their own rights and I will definitely explore these options. However, these particular resources deal specifically with ghost limiting as related to view distance.
The problem I am having seems to stem from the actual hard limits set on the maximum number of allowed ghosts. I was curious:
- Is it possible to increase the number of ghosted objects?
- If it were possible, what sorts of problems would this increase potentially introduce?
Also, bonus points for a clear explanation on how view distance affects ghosts in the first place. If I generate in a bunch of cubes and use the command ServerConnection.getGhostsActive() I can see the current number of ghosts. Check. But if I then reduce the viewDistance to an absurdly low amount (such as 10) and run far away from the generated cubes...the command will still show the same number of ghosts, regardless of my distance from them.
Cheers guys, as always thanks in advance for any help.
About the author
Skilled Artist and Musician. Intermediate Torque Developer.
#182
You may see a pattern emerging:
writeFlag returns the value that was written - so if we wrote a 1, we proceed to the inside of the if statement, where we can write some actual data to the client. This is all an optimisation - if the data protected by SomeMaskBit doesn't actually need to be updated on this tick, then we just send a single 0 bit, instead of including redundant data in the bit stream.
Right, so how do we network our cube list? I'll give you some pseudocode:
At least, that's what I used, but there are always many ways to solve a problem. Good luck, and call us if it doesn't make sense!
In other news, I'm still having raycast issues. The pistol seems to shoot straight through, despite giving the appropriate messages in the console that it's hitting cubes. This is very perplexing. The grenade launcher seems to sometimes? shoot through, but more often will just race straight up the wall and explode on top of the cubes.
09/28/2014 (4:58 pm)
Without further ado, find RenderMeshExample::packUpdate and have a look. It's already got two potential updates in it - one linked to TransformMask, and one to UpdateMask. TransformMask is set whenever we call setTransform on the object, and just updates clients with the object's new position. UpdateMask is used to synch changes we make in the editor.You may see a pattern emerging:
if(stream->writeFlag(mask & SomeMaskBit))
{
stream->writeStuff(myStuff);
}'mask' is the network mask we've been talking about - it's passed into the function because, as I said, it's not actually a property of the object. Anyway, what this pattern does is always write a single bit into the bit stream. The bit will be 1 if the mask contains the SomeMaskBit bit, and 0 otherwise. Basically, it's saying 'if we need to update the properties protected by SomeMaskBit, send a flag down the stream'. Note that we always write, even if we have to write a 0 to say 'nope don't need to update this property'.writeFlag returns the value that was written - so if we wrote a 1, we proceed to the inside of the if statement, where we can write some actual data to the client. This is all an optimisation - if the data protected by SomeMaskBit doesn't actually need to be updated on this tick, then we just send a single 0 bit, instead of including redundant data in the bit stream.
Right, so how do we network our cube list? I'll give you some pseudocode:
if(/*we need to update InitialUpdateMask*/)
{
// Send the length of cubeList.
// For each cube in cubeList,
// send its material number and
// send its position.
}See how you go translating that to actual code ;). Once you've done that, take a look at unpackUpdate. Note the existing code, and how it mirrors the packUpdate code. This makes sense - unpackUpdate has to read the bits that packUpdate writes, so they need to be symmetrical, otherwise you'll read garbage from the network.if(stream->readFlag()) // SomeMaskBit
{
myStuff = stream->readStuff();
}So we always read a single bit, then if it read a s a 1, we know that more data must have been written, so we go ahead and read that data. In your case, the code will look something like this:if(/*flag reads true*/) // InitialUpdateMask
{
// Read an integer N, and set the length of cubeList to it.
// For each N cubes in the list,
// read a matrial into the current cube and
// read a position.
}To help you do this, you might want to use the following BitStream functions: writeInt, readInt, writeRangedU32, readRangedU32; the following global functions: mathWrite, mathRead; and the following Vector functions: setSize.At least, that's what I used, but there are always many ways to solve a problem. Good luck, and call us if it doesn't make sense!
In other news, I'm still having raycast issues. The pistol seems to shoot straight through, despite giving the appropriate messages in the console that it's hitting cubes. This is very perplexing. The grenade launcher seems to sometimes? shoot through, but more often will just race straight up the wall and explode on top of the cubes.
#183
I'll report back after I go through all the code, apply it to my object, and(undoubtedly) figure out what questions to ask. Again, thanks man, this will be a big step for me!
Edit: Okay, after reading closely over the postings I've realized how you've set this lesson up :) Rather than going through the code and trying to apply it, I'll be attempting to plug it in myself initially so that I can gain a more genuine understanding of its use. Thanks, this should be a good exercise!
09/28/2014 (4:59 pm)
Oh wow, what a nice posting Danny! Thanks! It comes at a great time, as I've just finally seen the error in my ways relating to separating out the faces of the cube. Still a ton of work to do here, but at least I've managed to separate the faces. I'll need some switch/case code or something when I'm building the terrain, but it's all looking real good atm. I'll report back after I go through all the code, apply it to my object, and(undoubtedly) figure out what questions to ask. Again, thanks man, this will be a big step for me!
Edit: Okay, after reading closely over the postings I've realized how you've set this lesson up :) Rather than going through the code and trying to apply it, I'll be attempting to plug it in myself initially so that I can gain a more genuine understanding of its use. Thanks, this should be a good exercise!
#184
Anyways, I was able to follow the basic idea of what was going on but I noticed one major problem. The part where we declare the size of the cubeList. In that particular step, we are saying that the cubeList is N size and specifying an exact number. Doesn't this kind of defeat the purpose of the cubeList being able to dynamically change and update to each client? Or does the number specified there indicate the maximum number of cubes possible to be in the list, even if you may not be sending all of them at the time?
I've got the changes all in my .cpp now, but the game crashes on startup (of the level) so there must be more to this I'm taking it. I'm sure the changes are precise. I've added an extra material, since I have a materialNum2 in my .cpp for the top material, but it just follows suit of read/writing the RangedU32. In my case I am using (0, 3) since I have an index of 0 - 3 for the 4 materials in the texture atlas. I am unclear if this is the exact usage of the material numbers.
About the RayCast
Yeah lol the pistol shooting is very weird. I've noticed that if I have a flat plane of cubes and shoot kinda further off a bit it seems to collide and I see the 'hit dust'. Seems to work just fine at like 'mid-range'. If I shoot a cube any closer though, it will pass through it but it will still register a hit to the console.
09/28/2014 (7:30 pm)
Alright, so yea that was extremely vague...I was completely grasping at straws until I cracked open the cheat sheet. Had no idea at all how to use the functions or anything until I was able to actually put my eyes on it. Now that I have, though, it is pretty clear for the most part. It's easy to see how in packUpdate() we need to write the data and in unpackUpdate() we need to read it. I followed line by line, and it's just easier for me to learn in this way. Bear in mind I'm just starting with C++ and still struggling with the pointers and addresses. I've been doing a good bit of study around this stuff, but I suppose it just takes time to get it all right. Anyways, I was able to follow the basic idea of what was going on but I noticed one major problem. The part where we declare the size of the cubeList. In that particular step, we are saying that the cubeList is N size and specifying an exact number. Doesn't this kind of defeat the purpose of the cubeList being able to dynamically change and update to each client? Or does the number specified there indicate the maximum number of cubes possible to be in the list, even if you may not be sending all of them at the time?
I've got the changes all in my .cpp now, but the game crashes on startup (of the level) so there must be more to this I'm taking it. I'm sure the changes are precise. I've added an extra material, since I have a materialNum2 in my .cpp for the top material, but it just follows suit of read/writing the RangedU32. In my case I am using (0, 3) since I have an index of 0 - 3 for the 4 materials in the texture atlas. I am unclear if this is the exact usage of the material numbers.
About the RayCast
Yeah lol the pistol shooting is very weird. I've noticed that if I have a flat plane of cubes and shoot kinda further off a bit it seems to collide and I see the 'hit dust'. Seems to work just fine at like 'mid-range'. If I shoot a cube any closer though, it will pass through it but it will still register a hit to the console.
#185
I wondered just how comfortable you'd feel without seeing the code - anyway, as long as you can now read and understand it, I'm satisfied!
Also, yep, sounds like you're on track with the changes to add the second material. writeRangedU32 tries to write as few bits as possible in order to make sure it can store numbers with the range you need.
09/28/2014 (8:20 pm)
Oh shoot, I forgot there's one more change to make! Do you get an error about not being able to create something to do with graphics? I forgot to mention I added these two lines. Basically the client starts with a cubeList with 0 entries, and createGeometry doesn't like it.I wondered just how comfortable you'd feel without seeing the code - anyway, as long as you can now read and understand it, I'm satisfied!
Quote:Doesn't this kind of defeat the purpose of the cubeList being able to dynamically change and update to each client?We only perform this update once, when InitialUpdateMask is set (i.e. the first time the client becomes aware of the submatrix). If the server adds more cubes, the client won't be informed yet - I'm saving that for later, i.e. when I work out how to do it properly/easily :P. The number we send tells the client 'hey I have this many cubes in me, expect that many of them shooting down the line'. If we then code a way for the server to tell each client 'hey, somebody added a cube', then we can use the fact that cubeList is a vector and add another cube to that list. Kind of make sense?
Also, yep, sounds like you're on track with the changes to add the second material. writeRangedU32 tries to write as few bits as possible in order to make sure it can store numbers with the range you need.
#186
Okay, yep the InitialUpdateMask stuff makes sense. I understand, by your explanation, that this will be called the first time only. I guess what's most confusing here is where InitialUpdateMask originates from in the first place. It feels like we're saying, 'Check if this mask is dirty' but where'd the mask come from? The other masks (UpdateMask and TransformMask) I've been able to track down in the .cpp file as already being set:
Also, I need to be entirely clear I'm using this entire object correctly. Up until now, with no networking involved, I've been able to just call addCube() in the Submatrix::Submatrix() function (RenderMeshExample::RenderMeshExample :P) and anything I build there will show up if I call to create a Submatrix object in script. However, I noticed in your file you had no cubes added at all in your function up top. So, should I be trying to create this thing as empty initially? Just a discrepancy I noticed, wasn't sure if it was just for the example or if you are really using it this way? I mean, I'm to a point now that I can see how I can do all sorts of creative stuff in that top Submatrix() function to generate the terrain. Should I instead be shooting an empty Submatrix object to Torque and building it with addCube() in script? 0.o just when I had figured out how to generate just certain faces, this kinda confuses me.
Honestly, if you have a working RenderMeshExample.cpp that you are using for your tests just link me that and I'm sure I'd be able to connect the dots. I'm very good at 'reverse engineering' code if I just get my eyes on it in the first place. I can go line by line and notice differences easier than I can trying to piece together stuff bit by bit :D Added those 2 lines and I'm still getting a crash on mission startup, regardless of the cubes in my cubeList. In other words, I've tested it with or without adding Cubes to the object initially.
09/28/2014 (9:03 pm)
Quote:I wondered just how comfortable you'd feel without seeing the code - anyway, as long as you can now read and understand it, I'm satisfied!I just learn best by example tbh. That's how AndrewMac was stepping me through it so well originally. He'd link me fully working code and I'd use that, see it work for myself, and connect the dots internally. Over time, as I go over it again and again each day, I am just getting extremely familiar with all of it. Like now I can break apart every single aspect of what we've covered and understand every single line of it. I don't do nearly as well with 'do it yourself' type of exercises. Don't get me wrong, I am learning really well; I just learn best with solid workable stuff. I'd rather the puzzle already be assembled so I can then disassemble it to discover how it all works. Trust me Danny, I've come this far and I'm still going! The last thing I want to do is just take the code, use it, and never look back. But if it's all working to begin with, I don't get frustrated, and the more I try to add in my own stuff the better I understand it. Hope that makes sense haha :P
Quote:Kind of make sense?
Okay, yep the InitialUpdateMask stuff makes sense. I understand, by your explanation, that this will be called the first time only. I guess what's most confusing here is where InitialUpdateMask originates from in the first place. It feels like we're saying, 'Check if this mask is dirty' but where'd the mask come from? The other masks (UpdateMask and TransformMask) I've been able to track down in the .cpp file as already being set:
... setMaskBits( UpdateMask ); ... setMaskBits( TransformMask ); ...So it makes sense to me that these MaskBits are being checked. But in the case of the InitialUpdateMask we just pull it out of thin air and say, 'hey check if this is dirty'.
Also, I need to be entirely clear I'm using this entire object correctly. Up until now, with no networking involved, I've been able to just call addCube() in the Submatrix::Submatrix() function (RenderMeshExample::RenderMeshExample :P) and anything I build there will show up if I call to create a Submatrix object in script. However, I noticed in your file you had no cubes added at all in your function up top. So, should I be trying to create this thing as empty initially? Just a discrepancy I noticed, wasn't sure if it was just for the example or if you are really using it this way? I mean, I'm to a point now that I can see how I can do all sorts of creative stuff in that top Submatrix() function to generate the terrain. Should I instead be shooting an empty Submatrix object to Torque and building it with addCube() in script? 0.o just when I had figured out how to generate just certain faces, this kinda confuses me.
Honestly, if you have a working RenderMeshExample.cpp that you are using for your tests just link me that and I'm sure I'd be able to connect the dots. I'm very good at 'reverse engineering' code if I just get my eyes on it in the first place. I can go line by line and notice differences easier than I can trying to piece together stuff bit by bit :D Added those 2 lines and I'm still getting a crash on mission startup, regardless of the cubes in my cubeList. In other words, I've tested it with or without adding Cubes to the object initially.
#187
InitialUpdateMask is set by other classes in the engine. Right-click on it in Visual Studio and hit 'find all references', and you'll get a list of every place it's used in the engine. ...and now I've done that and amusingly, I can't find any call to setMaskBits(InitialUpdateMask) :P. I wonder, then, how it gets set - don't tell me there's a magic number somewhere...
Oh, what crash are you getting? The files I linked to you before are what I am testing with.
09/28/2014 (9:19 pm)
Quote:Should I instead be shooting an empty Submatrix object to Torque and building it with addCube() in script?Do what you like! If you want to write terrain generation in C++ then go ahead - if you want to do it all in scripts, do that. It'll basically be identical in every respect except where your code lives, as long as you call all your addCube before the object starts to ghost (so, as soon after it's created as possible).
InitialUpdateMask is set by other classes in the engine. Right-click on it in Visual Studio and hit 'find all references', and you'll get a list of every place it's used in the engine. ...and now I've done that and amusingly, I can't find any call to setMaskBits(InitialUpdateMask) :P. I wonder, then, how it gets set - don't tell me there's a magic number somewhere...
Oh, what crash are you getting? The files I linked to you before are what I am testing with.
#188
The error I get is:
D3DERR_INVALID CALL
Invalid call
Failed to allocate VB
Oh, also this error is only if I try to send the object empty (no addCubes).
If I try to start the mission with my object already built, it just crashes with a generic "Cubes_Debug.exe has stopped working". I'm sure it must be something to do with the newly added networking, since up until now I've been able to load up the missions with my generated cube-terrain.
09/28/2014 (9:37 pm)
See, the thing with using addCube() in script is that it doesn't work for me. Even if I do shoot the empty to Torque and try to build it that way, I'm not getting anything at all out of Torque if I try to call addCube() in script. I had mentioned this in a previous posting, but you seemed to believe it was due to no networking. But in that case, I wasn't trying to do anything dynamically. Just trying to build the object initially. I was wondering why it wouldn't work since I had the DefineEngineMethods in place etc. The error I get is:
D3DERR_INVALID CALL
Invalid call
Failed to allocate VB
Oh, also this error is only if I try to send the object empty (no addCubes).
If I try to start the mission with my object already built, it just crashes with a generic "Cubes_Debug.exe has stopped working". I'm sure it must be something to do with the newly added networking, since up until now I've been able to load up the missions with my generated cube-terrain.
#189
Hmm, the 'stopped working' error is less hopeful. See what you can glean from breakpoints.
09/28/2014 (9:46 pm)
Right, that's definitely the error I was getting. You should be able to click on the buttons and get it to drop you back into Visual Studio at the point where the error was raised, and inspect the values of your variables. I really think that the cubeList is empty on the client. Place some breakpoints in packUpdate and unpackUpdate to see what is actually getting transmitted - verify that the server sends X cubes (where X is the number you add), and that the client gets them all. You could also put a breakpoint in the addCube engine method to make sure it's all in working order.Hmm, the 'stopped working' error is less hopeful. See what you can glean from breakpoints.
#190
Since you are using those files you linked me for your own testing, I'm going to just re-use those for now and be sure they are working the same for me as they are for you first. Question: Since you are using the RenderMeshExample files are you dropping those into the /source folder or editing it directly in the source location? I'd like to try to have the same setup at runtime if possible.
09/28/2014 (9:55 pm)
By breakpoints you just mean add in some console echos? Since you are using those files you linked me for your own testing, I'm going to just re-use those for now and be sure they are working the same for me as they are for you first. Question: Since you are using the RenderMeshExample files are you dropping those into the /source folder or editing it directly in the source location? I'd like to try to have the same setup at runtime if possible.
#191
If you've never used breakpoints before you're in for a treat. Feast your eyes. Let me know if you need more explanation. I'd stick breakpoints just before the writeFlag/readFlag, and anywhere inside addCube.
EDIT: strangely that article doesn't mention. Click the gutter next to a line to set a breakpoint there (where the red dot is in the picture).
09/28/2014 (10:01 pm)
I just edited the original source files, rather than adding new ones.If you've never used breakpoints before you're in for a treat. Feast your eyes. Let me know if you need more explanation. I'd stick breakpoints just before the writeFlag/readFlag, and anywhere inside addCube.
EDIT: strangely that article doesn't mention. Click the gutter next to a line to set a breakpoint there (where the red dot is in the picture).
#192
EDIT:
Okay, got this:
*** Phase 2: Download Ghost Objects
Mapping string: MissionStartPhase2Ack to index: 1
c:\dev\torque3d\torque3d-cubes\engine\source\gfx\d3d9\gfxd3d9device.h(70) : Fatal - D3DERR_INVALIDCALL
Invalid call
Failed to allocate VB
The thread 'Win32 Thread' (0x1698) has exited with code 0 (0x0).
Cubes_DEBUG.exe has triggered a breakpoint
c:\dev\torque3d\torque3d-cubes\engine\source\gfx\gfxvertexbuffer.h(108) : Fatal - Can't get a lock with the end before the start.
The thread 'Win32 Thread' (0x680) has exited with code 0 (0x0).
Cubes_DEBUG.exe has triggered a breakpoint
First-chance exception at 0x10c0012b (Cubes_DEBUG DLL.dll) in Cubes_DEBUG.exe: 0xC0000005: Access violation reading location 0x00000000.
09/28/2014 (10:18 pm)
Okay was able to add some breakpoints, start the debug, and flip through the breakpoints. But I wasn't sure what I was supposed to be looking for :)EDIT:
Okay, got this:
*** Phase 2: Download Ghost Objects
Mapping string: MissionStartPhase2Ack to index: 1
c:\dev\torque3d\torque3d-cubes\engine\source\gfx\d3d9\gfxd3d9device.h(70) : Fatal - D3DERR_INVALIDCALL
Invalid call
Failed to allocate VB
The thread 'Win32 Thread' (0x1698) has exited with code 0 (0x0).
Cubes_DEBUG.exe has triggered a breakpoint
c:\dev\torque3d\torque3d-cubes\engine\source\gfx\gfxvertexbuffer.h(108) : Fatal - Can't get a lock with the end before the start.
The thread 'Win32 Thread' (0x680) has exited with code 0 (0x0).
Cubes_DEBUG.exe has triggered a breakpoint
First-chance exception at 0x10c0012b (Cubes_DEBUG DLL.dll) in Cubes_DEBUG.exe: 0xC0000005: Access violation reading location 0x00000000.
#193
Same thing was happening with my file. I took out all my scripts placing the objects, and the mission loaded fine. As soon as I called the console to create one, though, no dice. In both cases, commenting out the updated pack/unpackUpdate additions fixes it :)
EDIT: I'm also very curious as to how you've been adding cubes. Since I now have the exact files you've been testing with and they contain no calls to addCube() in the .cpp file, are you using a script to add them? If so, could you share the script? I've had no success adding any cubes with addCube() via script. Even with no console errors, and calling createGeometry() afterwards(also with no console errors) I have yet to see a single cube actually add via script. Not during runtime, which would indicate the networking was to blame, but even on scripts that run prior to the mission loading up. Please elaborate on how you have been adding cubes to test your castRays!
09/28/2014 (10:46 pm)
Well, just to be sure I just removed my custom files from the /source folder. I copied your .cpp and .h files and placed them directly in my engine/source/T3D/examples folder. So now I have your exact files in place in the engine (renderMeshExample.cpp and .h) and I'm getting the crash as soon as I attempt to add the object. Same error, with the included 2 lines you added. Also I did recompile it all after making the change. Same thing was happening with my file. I took out all my scripts placing the objects, and the mission loaded fine. As soon as I called the console to create one, though, no dice. In both cases, commenting out the updated pack/unpackUpdate additions fixes it :)
EDIT: I'm also very curious as to how you've been adding cubes. Since I now have the exact files you've been testing with and they contain no calls to addCube() in the .cpp file, are you using a script to add them? If so, could you share the script? I've had no success adding any cubes with addCube() via script. Even with no console errors, and calling createGeometry() afterwards(also with no console errors) I have yet to see a single cube actually add via script. Not during runtime, which would indicate the networking was to blame, but even on scripts that run prior to the mission loading up. Please elaborate on how you have been adding cubes to test your castRays!
#194
Observe this line in my version of RenderShapeExample. It's a total hack, but it lets me do this in my .mis file:
With the breakpoints - in addCube, you don't really need to look for anything. We know the C++ function is working correctly, we just need to be sure the script binding is good. With the breakpoints in the pack/unpackUpdate functions, you should step through the two for loops and make sure they happen the correct number of times.
09/29/2014 (12:01 am)
This is why I suggest putting a breakpoint in the addCube function. If it's ever actually called, you'll know about it.Observe this line in my version of RenderShapeExample. It's a total hack, but it lets me do this in my .mis file:
function Cubes::onAdd(%this) {
%this.addcube(0, 0, 0, 0);
}
// OBJECT WRITE BEGIN
new SimGroup(MissionGroup) {
...
new RenderMeshExample(Cubes) {
...
};
};
// OBJECT WRITE ENDI don't recommend this. If you're creating the submatrix objects from a script like I suggested before, then you should be able to call addCube right after creating the object:function makeTerrain() {
%obj = new RenderMeshExample() {...};
%obj.addCube(0, 0, 0, 0);
}With the breakpoints - in addCube, you don't really need to look for anything. We know the C++ function is working correctly, we just need to be sure the script binding is good. With the breakpoints in the pack/unpackUpdate functions, you should step through the two for loops and make sure they happen the correct number of times.
#195
First off, I was finally able to add a cube using addCube(). I could do this in 2 ways:
The hacky way(not recommended) works fine but the cube is invisible (no material). In the function (recommended) works too but the cube is still invisible(no material).
In both cases, the material was initialized upon creation of the object. This is very weird, though, because when I was creating my objects before they had the material as they should have...but addCube() wouldn't work. Here's the difference in what you just showed me and what I've been doing:
Firstly, I've been using a for statement in my Submatrix.cpp to call addCube() and build it there in C++ before we even hit the script. Now, if the object gets called it's built just like it was told to in the .cpp file. But this causes addCube() not to work at all if you call it from script. Idk why, but it doesn't.
I've been creating a new SimGroup(World) and then a 2nd SimGroup(Matrix) in my .mis file. These were empty, like so:
So then I had a function to create the object, but it wasn't just a function - it had the simgroup out front:
Either you build it in the .cpp file, have materials, and sacrifice the ability to call addCube() in script. or
Leave it empty in the .cpp file, be able to call addCube() in script, but have no materials.
lol!
About the pack/unpack, I'm not sure what's going on there either but if I am using the exact same .cpp and .h files as you are and getting a constant crash(only when creating a submatrix object) I'm not sure why I need to debug that. It's good debug practice I guess, but it's pretty obvious to me something's not right there. I mean, if we have the same build (dev 3.5.1-basically 3.6) and the same files we should have the same results?
09/29/2014 (1:25 am)
Okay, there is something very strange going on with this entire implementation of this new object. I've been able to follow your advice in the last post and now I have some very interesting results.First off, I was finally able to add a cube using addCube(). I could do this in 2 ways:
In both cases, the material was initialized upon creation of the object. This is very weird, though, because when I was creating my objects before they had the material as they should have...but addCube() wouldn't work. Here's the difference in what you just showed me and what I've been doing:
Firstly, I've been using a for statement in my Submatrix.cpp to call addCube() and build it there in C++ before we even hit the script. Now, if the object gets called it's built just like it was told to in the .cpp file. But this causes addCube() not to work at all if you call it from script. Idk why, but it doesn't.
I've been creating a new SimGroup(World) and then a 2nd SimGroup(Matrix) in my .mis file. These were empty, like so:
new SimGroup(World) {
canSave = "1";
canSaveDynamicFields = "1";
new SimGroup(Matrix) {
canSave = "1";
canSaveDynamicFields = "1";
};
};So then I had a function to create the object, but it wasn't just a function - it had the simgroup out front:
function World::Submatrix(%this, %x, %y, %z)
{
%obj = new Submatrix()
{
material = "atlas01";
position = %x SPC %y SPC %z;
rotation = "1 0 0 0";
scale = "1 1 1";
};
Matrix.add(%obj);
%obj.addCube(0,0,0,0);
}Like I said, this doesn't work. By all rights it should though, shouldn't it? The only differences I see here is that it's a function of the SimGroup calling to the 'child' object and I already had the Submatrix object's initial cubes built in source before calling here to add another one. In any case, this above does not work at all(to call addCube)...but it does have materials. So it's a catch22. You have 2 options:lol!
About the pack/unpack, I'm not sure what's going on there either but if I am using the exact same .cpp and .h files as you are and getting a constant crash(only when creating a submatrix object) I'm not sure why I need to debug that. It's good debug practice I guess, but it's pretty obvious to me something's not right there. I mean, if we have the same build (dev 3.5.1-basically 3.6) and the same files we should have the same results?
#196
So I ran through and tried not adding any cubes in script, and I got the crash again - turns out I forgot an important point. In unpackUpdate, we call createGeometry() even if no cubes are sent. So that's not good. So to fix your crash, you'll probably want to add a check for cubeList.size() > 0, either at the top of createGeometry, or before calling it in unpackUpdate.
The other reason I wanted to to put breakpoints in the packUpdate function is to make sure that the cubes are being added with addCube before the object is sent across the network - if the scripts add the cubes too late, then the initial update has already happened and the client won't know about further changes!
Here's the code with those changes, and with a .mis file that you should be able to drop into your game to test. It's called 'cubes test' and uses the simple hacky approach I described above, with an onAdd callback. Just see if that works, and we can go from there!
09/29/2014 (2:29 am)
Well, you need to debug it to figure out why the crash is happening! It's no use wringing our hands about it - a crash means there was a wrong value, or the code wasn't doing what we expected, so we have to step through and see if it all makes sense - or at least see what condition caused things to mess up so badly.So I ran through and tried not adding any cubes in script, and I got the crash again - turns out I forgot an important point. In unpackUpdate, we call createGeometry() even if no cubes are sent. So that's not good. So to fix your crash, you'll probably want to add a check for cubeList.size() > 0, either at the top of createGeometry, or before calling it in unpackUpdate.
The other reason I wanted to to put breakpoints in the packUpdate function is to make sure that the cubes are being added with addCube before the object is sent across the network - if the scripts add the cubes too late, then the initial update has already happened and the client won't know about further changes!
Here's the code with those changes, and with a .mis file that you should be able to drop into your game to test. It's called 'cubes test' and uses the simple hacky approach I described above, with an onAdd callback. Just see if that works, and we can go from there!
#197
I just got a chance to try out your updates, and they work! I see the check for the size of the list prior to createGeometry(). Also the change of the numCubes from an int to a ranged value was a great idea! I think that allows for much more flexibility no matter what we do with it in the future, nice. I hadn't had a chance to dig deeper as I'm a little busy today, but I did notice the material was working too! Woot! Thanks man, I'll take a closer look and see if I can figure out how you got the material to update properly.
EDIT: Okay, I've added all the changes to my custom object .cpp files and it all works great! Not only is the crash fixed, but I am now able to build the geometry in any way as I want to! It doesn't matter if I build it in C++, script, .mis file, whatever! Things that should have been working all this time are now working!! I think it all just comes down to the networking. Due to Torque's client/server architecture, without at least the minimal networking bits(InitialUpdate) it was causing problems all along.
This is fantastic Danny, thank you so much! I can now see the importance of the pack/unpack updates very clearly. Pretty much everything needs to be networked properly or it's going to cause problems. I bet that the castRay problems have to do with no maskbits being sent relating to collision...but of course that's just a guess :)
09/29/2014 (1:02 pm)
Okay, that makes sense. Thanks for bearing with me Danny, this is a pretty big step lol. I went from not using C++ at all to writing networking updates and generating mesh geometry...all within the span of a little over a week. So, I think we're doing pretty good so far :)I just got a chance to try out your updates, and they work! I see the check for the size of the list prior to createGeometry(). Also the change of the numCubes from an int to a ranged value was a great idea! I think that allows for much more flexibility no matter what we do with it in the future, nice. I hadn't had a chance to dig deeper as I'm a little busy today, but I did notice the material was working too! Woot! Thanks man, I'll take a closer look and see if I can figure out how you got the material to update properly.
EDIT: Okay, I've added all the changes to my custom object .cpp files and it all works great! Not only is the crash fixed, but I am now able to build the geometry in any way as I want to! It doesn't matter if I build it in C++, script, .mis file, whatever! Things that should have been working all this time are now working!! I think it all just comes down to the networking. Due to Torque's client/server architecture, without at least the minimal networking bits(InitialUpdate) it was causing problems all along.
This is fantastic Danny, thank you so much! I can now see the importance of the pack/unpack updates very clearly. Pretty much everything needs to be networked properly or it's going to cause problems. I bet that the castRay problems have to do with no maskbits being sent relating to collision...but of course that's just a guess :)
#198
09/29/2014 (7:18 pm)
Great, I'm glad those changes made it work for you! You're doing great, by the way - I'm impressed with the speed which you've grokked everything we've thrown at you. I've got a busy day or two coming up but when that's over - unless someone else gets to it first - we'll see if we can network changes dynamically. This will be a bit more involved, I suspect, but should be a lot of fun :D.
#199
I know you're busy Danny, we've got 3.6 right on the horizon :) In light of this recent victory, I've begun the conversion to isometric for my prototype. It's all going very smoothly - at least this stuff I'm pretty comfortable with! Until we get the raycasts working on the new objects I'm just keeping a ground plane under the generated terrain to 'fake' a hit so my AIPlayer moves. lol! It gets the job done for now :P
Looking forward to learn more about the networking Danny, thanks again and good luck with 3.6!
09/29/2014 (7:43 pm)
Well, to be fair Danny, you guys have been excellent instructors! I wouldn't have gotten half this far without all of you guys' support and dedication to help. It truly is a fantastic time for me. Learning more of the ins and outs of the source, digging deeper into new syntax(C++), and seeing a genuine side of the community that I hadn't noticed before. I know you're busy Danny, we've got 3.6 right on the horizon :) In light of this recent victory, I've begun the conversion to isometric for my prototype. It's all going very smoothly - at least this stuff I'm pretty comfortable with! Until we get the raycasts working on the new objects I'm just keeping a ground plane under the generated terrain to 'fake' a hit so my AIPlayer moves. lol! It gets the job done for now :P
Looking forward to learn more about the networking Danny, thanks again and good luck with 3.6!
#200
In the meantime, I've managed to take huge strides with C++! Not only am I using C++ to create my own custom datablocks for use in Torque, I now have written several custom functions that generate noise for my blocks. All in C++!! What this means is, on load of the level, my blocks are being placed randomly so that the 'terrain' of blocks is random each time. I've enjoyed learning the C++ syntax, and finally it's starting to 'click'. I'm starting to realize how easy it can be to hunt down examples in the source and apply them for my own benefit.
castRay
In any event, I've been working real hard to figure out the problem with the castRay() function Danny initially came up with. After a lot of trial and error, referencing other castRay() function examples throughout the source, I've come up with this:
Now this is a 'slight' improvement over what I was getting with the original castRay. Only 'slight' because the hits have potential to occur earlier than expected. However, I am seeing a hit either on target or a bit early in all cases...instead of the projectiles passing through the cubes and hitting a random cube further out.
Anyone have any ideas? Better yet, has anyone ever used the 'RenderMeshExample' and actually added buildConvex() and castRay() functions to it that work? The provided 'example' is incomplete in that it isn't networked, has no convex, and no castRay.
10/21/2014 (3:13 pm)
It's been about 3 weeks since last I posted here, and almost a month since Andrew said he 'might be another day or two' getting back. I feel he's abandoned the cause :/ In the meantime, I've managed to take huge strides with C++! Not only am I using C++ to create my own custom datablocks for use in Torque, I now have written several custom functions that generate noise for my blocks. All in C++!! What this means is, on load of the level, my blocks are being placed randomly so that the 'terrain' of blocks is random each time. I've enjoyed learning the C++ syntax, and finally it's starting to 'click'. I'm starting to realize how easy it can be to hunt down examples in the source and apply them for my own benefit.
castRay
In any event, I've been working real hard to figure out the problem with the castRay() function Danny initially came up with. After a lot of trial and error, referencing other castRay() function examples throughout the source, I've come up with this:
bool Cell::castRay(const Point3F &start, const Point3F &end, RayInfo *info)
{
RayInfo shortest = *info;
RayInfo localInfo;
shortest.t = 1.0f;
localInfo.generateTexCoord = info->generateTexCoord;
// Used for collideLine
Box3F cube;
// Normal vector of the ray collision.
// Point3F n;
VectorF n;
// Check each cube.
for (U32 i = 0; i < cubeList.size(); i++)
{
// Create our box.
cube.set(cubeList[i].position, cubeList[i].position + Point3F(1, 1, 1));
// Now collide against the box that represents this cube.
if (cube.collideLine(start, end, &shortest.t, &n))
{
localInfo.object = this;
if (localInfo.t < shortest.t)
{
shortest = localInfo;
}
}
if (shortest.object == this)
{
// Copy out the shortest time...
*info = shortest;
Con::printf("Hit Cube Number: %d", i);
return true;
}
}
return false;
}Now this is a 'slight' improvement over what I was getting with the original castRay. Only 'slight' because the hits have potential to occur earlier than expected. However, I am seeing a hit either on target or a bit early in all cases...instead of the projectiles passing through the cubes and hitting a random cube further out.
Anyone have any ideas? Better yet, has anyone ever used the 'RenderMeshExample' and actually added buildConvex() and castRay() functions to it that work? The provided 'example' is incomplete in that it isn't networked, has no convex, and no castRay.
Torque Owner Daniel Buckmaster
T3D Steering Committee
As you're probably painfully aware of by now, Torque doesn't share information between the client and server by default. Every server object may or may not have a 'ghost' of itself on each client connected to the game. To minimise bandwidth usage and processing, Torque uses 'mask bits' to decide what information needs to be sent to clients to update their ghost copies of objects. Each object has a 'mask', which is just a 32-bit integer. When an object changes its state on the server (or at least, part of its state that clients should be able to see), it sets to 1 one of the bits in its flag - each bit corresponds with a specific flag, and each class gets to decide what they mean. For example, there's an InitialUpdateMask, which is the first mask defined in SceneObject, which is set the first time an object is ghosted to a client. ShapeBase defines mask bits for its animation threads, and so on.
It's important to understand here that an object's mask isn't simply a property of that object. A bit mask is unique per object and per client connection. In cases of dropped packets or other networking shenanigans, which may happen to one connection but not all, Torque uses masks to cleverly ensure no data is lost.
But we don't really need to understand all that. All we need to do is implement two functions: packUpdate and unpackUpdate. The first gets called on the server when the object's mask is dirty (changes need to be sent), and the second gets called on the client when receiving a packet from the server. Basically these two methods are your class's network serialisation. You condense all the relevant information the client needs into bits in a stream, which gets squirted through the intertubes, and then unpacked and interpreted on the client.
Before we start coding, I'm going to go over my network design for this class. Originally, Andrew talked about doing something similar to the shape replicator classes. They manage to achieve basically no network overhead. They use a 'seed' number to randomly place objects, so all they need to send across the network is the seed, and then the client can create the objects in exactly the same (random) locations using that seed number. No need to send a list of objects or anything.
What we're doing here is a little different. Since I've no doubt you'll want to support editing your objects, you can't just reconstruct the terrain from a seed - because it might have been modified before a particular client joins the game. One way of dealing with this is to send the seed, and also a list of modifications - for example, if player A joins, and digs a hole, then player B joins, player B would receive the seed, generate the original random terrain, and then receive a message saying 'player A removed these blocks', and remove them also. Of course, you'll have to do something like this to deal with players dynamically adding and removing blocks anyway - and you don't want top have to re-send an entire chunk when just one block is added or removed.
But I chose not to implement that 'seed + change' method right now for a couple of reasons. 1) keeping that list of changes isn't trivial. 2) depending on your game, you might completely terraform the terrain, so the list of changes would be so big you might as well have sent the whole terrain naively.
So what we're going to do for now is use the InitialUpdateMask to ensure that a new client joining the game gets the exact list of blocks that currently exist in the server object. Later on, we'll deal with dynamic modifications.