-
-
- (8145)
-
Member
-
Posts
1,763
|
Re: Global scoreboard component, by Spyn Doctor
|
Luke, in the following, I assume that you are using my original code (with the changes from my previous post), because of course if you made some changes, then the following speculation of mine may be off... :-)
If I understand you correctly, then this: radiangames:UNKNOWN: MANAGER DISABLED (via stop) UNKNOWN: MANAGER ENABLED Loaded save file. The thread 0xf900004c has exited with code 0 (0x0). UNKNOWN: Looking for server UNKNOWN: Server found: Sir Vizor UNKNOWN: Initiating stop UNKNOWN: MANAGER DISABLED (via stop)
is the debug output that you get when you try to exit the game immediately after starting it, so that the game exit overlaps with the loading thread, which also starts the score sharing, right?
From those messages, I think that you problem may be, that you are calling OnlineDataSyncManager.stop(...) too often. Seems like stop was called 3 different times. This may indeed cause problems.
As I wrote in the readme, stopping the score manager is a bit tricky, because when you stop it, it actually has to remain active for a few more frames so that the session can safely be finished and closed. Which means that after the call to stop returns, the manager is not necessarily completely stopped yet, but is, for the time being, still running and will stop itself a few frames later. But this in turn means, that if you call stop again in these subsequent frames, then these additional stop-calls may interfere with the stop that is already ongoing.
You should identify where in your code you have calls to "stop", and somehow make sure, that for each given situation, only one of these places actually does a call to stop.
For example, I can imagine the following scenario:
Code location 1: This code is executed when the player selects the "Return to Game Library" menu option. This is where the game is supposed to exit, so here you have a call to "stop" with a non-null AfterStopDelegate (and in the delegate you have the call to Game.Exit()) and you pass "true" for the second argument "gameExiting".
Code location 2: This code is executed when the player that was used to start the component signs out (sign out event) or otherwise returns to the "Press Start to Play" screen. So here you have a call to "stop" without an AfterStopDelegate, and the "gameExiting" argument is false. In addition to the call to "stop", you also have some other cleanup code here which is supposed to run whenever a player becomes inactive and the game returns to the "Press Start to Play" screen (for example you may save the player's data at this point).
Now assume that when you programmed this, you thought that this cleanup code that you have in location 2 (to save the player data, or whatever) is actually something that should also be executed on game exit. Then you could have programmed it so, that on game exit, you not only execute the code at location 1 (to exit the game) but also the code at location 2 (to clean up after the player). But since both locations do a call to "stop", you now call "stop" twice. You could fix this for example by having a flag that you set to "true" in location 1, to signal that stop was already called, and in location 2 you would only call stop if the flag was still "false".
Of course I don't know if this really applies to your game, without seeing the code, but that's an example of a scenario how you could accidentally call "stop" multiple times. If in doubt, just set a breakpoint in your stop method and observe from where it is being called whenever you exit the game. It should be called only once. If it is called multiple times, then you must somehow remove all but one of the stop calls (or set a flag so that these other calls are not made during game exit, or similar). The one remaining stop call should be the one with the AfterStopDelegate that contains Game.Exit(), and with gameExiting == true.
EDIT: After writing the above, I decided to make it a bit easier to detect, if after a call to "stop" the stop is still being processed, or if the manager has already stopped completely. So I added the following method to OnlineDataSyncManager:
| public bool isStopping() | | { | | return mAction == Action.EXIT_THREAD || mAction == Action.DISABLE; | | } | and I added this to the readme:
IMPORTANT: When the call to stop() returns, then this does *not* necessarily mean that the manager is already stopped completely - it may still need a few more frames. You must *not* call stop() or start() again before it has stopped completely. In most cases this is not a problem, because the situations in your game where you want to start or stop the manager are usually in separate locations, so that there are always at least a few seconds (and thus many frames) between them (for example during which the player has to navigate a menu, or similar). But if you want to be absolutely sure that the processing of a stop-call has finished, query the isStopping() method: This method returns "false" if currently the manager is either running normally or is completely stopped. It returns "true" if the manager is currently in the process of stopping, i.e. while it is no longer running normally but also not yet completely stopped. So after a call to stop(), this method may return "true" for a few frames, before it returns "false" again. During the time it returns "true", you must *not* call stop() or start(). In combination with the Enabled property, you can exactly determine the current state of the manager: isStopping() == false && Enabled == true -> Manager is running normally isStopping() == false && Enabled == false -> Manager is completely stopped isStopping() == true -> Manager is currently in the process of stopping
(this change is also already online in the latest download package)
Doc
|
|
-
|
|
Re: Global scoreboard component, by Spyn Doctor
|
Hi Doc...
Thanks so much for this component. It's really wonderful of you to share, and I hope I can "pay it forward" someday.
Some quick questions, specifically in regard to sharing large datasets (thousands of entries with multiple columns).
If a transfer is interrupted, is the portion of data that was transferred kept? Tracing through the code, I think the answer is "yes".
Would there be any benefit to trying to compress the data before sending it? Since it's sending each entry one at a time, I suspect that the overhead of transferring each entry individually outweighs the benefits of compressing/decompressing the data...
Any other strategies that might be used to minimize transfer time? (Aside from the SLEEP configuration point, of course).
Thanks again!
Adman
|
|
-
-
- (8145)
-
Member
-
Posts
1,763
|
Re: Global scoreboard component, by Spyn Doctor
|
Adam Spragg Games:If a transfer is interrupted, is the portion of data that was transferred kept?
Yes. Adam Spragg Games:Would there be any benefit to trying to compress the data before sending it?
Since the data is transferred entry-by-entry, you can not simply compress your whole data before transfer, because then you would end up with one monolithic compressed data block from which you could no longer extract the individual entries for transfer. You can however compress each individual entry, if you can find a compression method that actually gives you a smaller data size than the uncompressed entry. For example, if you have lots of bools, you might encode then as individual bits, instead of one-byte-per-bool, and similar stuff.
Doc
|
|
-
-
- (4247)
-
Member
-
Posts
1,057
|
Re: Global scoreboard component, by Spyn Doctor
|
Spyn Doctor:]Since the data is transferred entry-by-entry,
does that mean that each highscore you send is contained in its own packet?
|
|
-
|
|
Re: Global scoreboard component, by Spyn Doctor
|
Spyn Doctor:For example, if you have lots of bools, you might encode then as individual bits, instead of one-byte-per-bool, and similar stuff.
Sure... makes sense.
I was thinking of using something like GZipStream to compress each entry before sending... but I really can't imagine that turning a, say, 50 byte entry into a smaller entry, would make a whole lot of difference. When I get some time, I'll play around and see if that's worth doing.
I have a followup question about the SLEEP_DURATION setting and testing. When we do our development work, we're going to be testing via SYSTEM LINK on our own systems for the most part. How much will things change when we remove the SYSTEM LINK setting and work off the LIVE servers? Is the best SLEEP setting under SYSTEM LINK also going to be the best setting for LIVE? Are the total transfer times more or less equivalent?
Thanks again.
Adman
|
|
-
-
- (8145)
-
Member
-
Posts
1,763
|
Re: Global scoreboard component, by Spyn Doctor
|
Riddlersoft: Spyn Doctor:]Since the data is transferred entry-by-entry,
does that mean that each highscore you send is contained in its own packet?
No and Yes.
"No", because when I wrote "entry-by-entry", I didn't necessarily mean that one entry equals one highscore. My component separates the network transfer part from the part that stores/maintains the actual score lists. As far as the network part is concerned, it only sees transfer records (and by "entry" I meant one such transfer record). Each transfer record is indeed sent as its own packet. However, what data you put into that transfer record depends entirely on your own implementation of IOnlineSyncTarget and its methods writeTransferRecord and readTransferRecord. If you want, you are free to transfer several highscores in a single record (packet).
"Yes" however, because the sample implementation of IOnlineSyncTarget that I included in my download (i.e. the class TopScoreListContainer) is indeed implemented in such a way, that each transfer record contains exactly one highscore. Adam Spragg Games:I was thinking of using something like GZipStream to compress each entry before sending... but I really can't imagine that turning a, say, 50 byte entry into a smaller entry, would make a whole lot of difference.
Actually, I would think that if you try to zip such a small amount of data like 50 bytes, that the zipped result would most likely be larger than 50 bytes, because of the zip-overhead.
I thought about this some more. The following would absolutely be possible, although I don't know if it would make such a difference, regarding the transfer duration: At the beginning of the transfer, you could write all highscores that you want to transfer to a byte array and then zip this whole array. Because of the larger amount of bytes (in comparison to just a single entry) the zipped results would certainly be smaller then the original array. But they would probably still be too much to transfer at once. Therefore you could divide this zipped array into convenient chunks (I don't know, maybe 100 or 200 bytes each, or so), and transfer each of these chunks as one of the transfer records (see above). On the receiving side, you would of course not be able to unzip these chunks individually, but you could re-assemble them into the full zipped array (once the last chunk has arrived) and then unzip it, to then retrieve the actual individual entries from the complete unzipped array. That would certainly reduce the number of bytes that would have to be transferred (=faster transfer) and by toying around with the chunk size, you could also try to find an optimum between packet size and packet count, for the fastest possible transfer. The main disadvantage would of course be, that then the transfer would be an all-or-nothing affair: The actual highscores could only be extracted on the receiving side once the last chunk was received. If the sender goes away too early, all the already transferred data would be useless. Adam Spragg Games:How much will things change when we remove the SYSTEM LINK setting and work off the LIVE servers?
Quite a lot, in my experience. LIVE is way slower than local network. If you really want to do optimization work, you should really do it over Xbox LIVE. But anyway, the SLEEP_DURATION is not so much something that you should adjust to compensate for differences on the network. This parameter is simply there to make sure that the worker thread does not hog the CPU too much. The optimal value for this therefore really depends on how much your game needs the CPU, and which CPU load you can run the worker thread with before the main game thread starts to drop frames.
Doc
|
|
-
|
|
Re: Global scoreboard component, by Spyn Doctor
|
I believe the issue being raised is if we can achieve a higher transfer rate, we could do a lot more: - more scoreboards - more stats to share - more scores in the scoreboards - etc. Removing the limitation would be be amazingly great in a number of ways.
My idea was to share random scores, and not in high-to-low order, so when share time is restricted, we still get all scores being shared. And with multiple clients, this means we should eventually get them all even if ALL clients had restricted share time. (Think torrents.)
Regarding the zipped data overhead: There would be no overhead. The code knows how to deal with the data, so there is no overhead in describing it. If the packed data itself needs description, and it's too large, the solution is to remove it, and settle on unique implementation that does not require any. I already have a routine to do so with scores and Gamertags. Consider that you don't need 1 byte (or 2 bytes in Unicode) to store a single Gamertag character, you only need 6. Also, Gamertags are not always 15 characters long, so 4-bits could store its length, followed by 6-bits per character.
The problem is that I ran into was the ridiculous loading/saving/compression/decompression time problems with this, and I never arrived at a conclusion at what this was, or how to solve it. I believe the code itself is what is slow -- even though code is a million times faster than HD, so this should not be the case, even if you have an unimaginably slow compression/decompression algorithm. But dealing with even 10,000 scores in this way just takes seconds upon seconds, like 10 or more, and it's foolish. It should be instant, and I am not sure why it is not. I believe it's because the code in C# does not allow direct access to a data stream of bytes, and you cannot merely read in X number of bytes from disk into a byte buffer, and then access it as such (as an array of bytes, or an array of bits). I've spoken to Shawn Hargreaves about this on these forums, and he stated there's no way around this. But I just want direct access. I don't care if it's safe code or not, the CPU can handle if I screw something up. This is just really upsetting, and restrictive, and I wish I had time to set aside to solve this out optimally given these restrictions, since I believe this could really help.
|
|
-
|
|
Re: Global scoreboard component, by Spyn Doctor
|
Doc, if you'd like to have my routines for compression in this manner, hit me up on email (jason@xona.com) and I will be happy to send it.
|
|
-
|
|
Re: Global scoreboard component, by Spyn Doctor
|
I did some experimentation on using GZipStream to compress entries.
For testing, a single entry contains a string (Gamertag), and some data (a timestamp (8 bytes), ushort (2 bytes), and byte (1 byte)).
With the Gamertag of "Guest", this comes out to 17 bytes. As you surmised, if you compress this single entry, the compressed byte array is 148(!!!) bytes.
However, taking a larger list (thousands of entries) and compressing them all together went from around 62K to about 19K. This feels fairly typical to me.
I guess it's a personal choice for me to weigh faster (but all-in-one-shot) transfers vs. slower but partial transfers...
|
|
-
-
- (8145)
-
Member
-
Posts
1,763
|
Re: Global scoreboard component, by Spyn Doctor
|
JasonD:Regarding the zipped data overhead: There would be no overhead.
When I wrote about "zipping data" and its overhead, I meant compression using typical "zip" or "gzip" algorithms (Lempel-Zif/Huffman...). With those algorithms, you always have some overhead, so with the wrong kind of data (keyword: high entropy), or if the data consists only of very few bytes, the zipped result can actually be larger than the original data. EDIT: While I wrote this, Adam Spragg Games made his post above, illustrating this.
The kind of overhead-free compression that you mention is a different beast. It is similar to what I suggested a few posts back (packing bools into individual bits, instead of using a full byte for each bool). It requires a thorough analysis of the data in question and a custom-made algorithm to compress exactly this data (in comparison to more general zip-algorithms, which are supposed to compress any given byte stream of unknown format/content). This kind of custom compression is of course totally possible without overhead and can definitely be used to compress the highscore data before transfer.
And thanks for your offer to give me your code, but I currently have no plans of extending my sample code in this direction. My implementation of the IOnlineSyncTarget interface (see TopScoreListContainer, TopScoreList and TopScoreEntry) is after all just a sample of how you can implement this interface. For many uses, this sample implementation is actually good enough to be used without changes, but for anyone with specific requirements (add compression, avoid garbage with object pooling, etc.), it may be necessary to program his own implementation of IOnlineSyncTarget, or at least enhance my sample implementation. But I leave this as an "exercise for the reader". :-) The OnlineSyncManager itself will happily transfer whatever data you feed to it. Of course if anyone here implements such improvements, you are welcome to share your code here with others!
Doc
|
|
-
|
|
Re: Global scoreboard component, by Spyn Doctor
|
In the release notes, Herr Doctor says that his component doesn't have to be used to share high scores. It could be used to share other kinds of data.
Many people have used this component to share high scores. I'm currently working on a game that uses this component to share actual game/world data to create a kind of pseudo-persistent online world. I'm curious if anyone else has done anything similar?
|
|
-
|
|
Re: Global scoreboard component, by Spyn Doctor
|
Okay, I just started using this. Just downloaded it today, so I'm sure I have the most up-to-date version. And implementing it has gone smooth for the most part. Now, I'm ready to test it.
Of course, I only have one CC profile, so I have to test it from my computer (Which now has a local windows live profile called "tester2") using the local profile. Then, like it said in your docs, I ran ran a version on the Xbox 360. Now, the PC version has the compilation SYSTEM_LINK_SESSION. But, the Xbox version doesn't. Is that how it's supposed to be set up?
Anyway, I ran them both. Fiddling with it each and every way, but neither session connected. Any help?
OUTPUT FOR XBOX:
UNKNOWN: MANAGER ENABLED
UNKNOWN: Looking for server
PlayerMatch
A first chance exception of type 'System.NullReferenceException' occurred in XNALibrary.dll
UNKNOWN: New local entry
UNKNOWN: Starting session
SERVER: Server time to live: 342.303sec
SERVER: Waking up server and again accepting all hosts
SERVER: Server timed out
SERVER: Session disposed
UNKNOWN: Looking for server
PlayerMatch
UNKNOWN: Starting session
SERVER: Server time to live: 362.749sec
SERVER: New local entry
SERVER: Waking up server and again accepting all hosts
SERVER: Server timed out
SERVER: Session disposed
UNKNOWN: Looking for server
PlayerMatch
UNKNOWN: Starting session
SERVER: Server time to live: 397.777sec
A first chance exception of type 'System.NullReferenceException' occurred in XNALibrary.dll
The program '[6389] Managed' has exited with code 0 (0x0).
|
|
-
|
|
Re: Global scoreboard component, by Spyn Doctor
|
Are both versions of the game supposed to be running in System Link? Or just the version with the local gamertag?
Also: The "PlayerMatch" text in the output is a System.debug I wrote to tell me which Network Session I was using. The Xbox was using PlayerMatch, while the Windows was on SystemLink. I think that's how it's set up, right?
Oh, it's been a LONG day.
|
|
-
-
- (8145)
-
Member
-
Posts
1,763
|
Re: Global scoreboard component, by Spyn Doctor
|
DualOpAmp29:Are both versions of the game supposed to be running in System Link?
Yes. Both versions must use the same type, otherwise they can't see each other. SystemLink essential means your local network (LAN), while PlayerMatch means the Xbox LIVE network. So if your PC version networks on the LAN, but the Xbox networks on Xbox LIVE, it won't work. You would use SystemLink for local testing (between Xbox/PC), and then you change to PlayerMatch before you do the build that you upload for playtest/review.
Doc
|
|
-
|
|
Re: Global scoreboard component, by Spyn Doctor
|
Well, its taken me the whole day, but I have the transfer working. But, it seems the Xbox version isn't receiving the high scores from the PC. Even though the PC receives the scores from the Xbox. Its kind of strange.
|
|
-
-
- (8145)
-
Member
-
Posts
1,763
|
Re: Global scoreboard component, by Spyn Doctor
|
If only the PC receives scores, but not the Xbox, then something must be wrong with your integration. The debug output should tell you where the communication goes wrong. Unfortunately I can't help more without more data/info.
Doc
|
|
-
-
- (8145)
-
Member
-
Posts
1,763
|
Re: Global scoreboard component, by Spyn Doctor
|
I got a question via email about if/how it would be possible to use my sharing component to share two different sets of data via the same OnlineDataSyncManager. I thought that the answer may be general interest, so here it is:
First the scenario: A game has score data that it wants to share. For this it is using the TopScoreListContainer class, which of course implements IOnlineSyncTarget. The game also has a level editor, and it wants to share the data of user created levels too. For this it has a UserLevelData class, which also implements IOnlineSyncTarget.
The game therefore has the following two members:
| TopScoreListContainer mScoreContainer; | | UserLevelData mUserLevels; | | Now of course it would be possible to call OnlineDataSyncManager.start(...) first with the mScoreContainer object, and then at another time stop the sync-manager again and then call start(...) again, this time with the mUserLevels object.
But this is very cumbersome and also not very efficient. And it would also be *very* hard to synchronize the two peers so that each of them starts the sync-manager with the same IOnlineSyncTarget at the same time.
A better solution is something like the following:
(IMPORTANT DISCLAIMER: The following code is untested and may even contain syntax errors, as I'm simply typing this into the text editor from memory!)
Create *another* class that implements IOnlineSyncTarget, let's call it SyncTargetContainer, so that the game has one more member:
| SyncTargetContainer mSyncTargets; | | | For the constructor of this class, you would pass the mScoreContainer and mUserLevels objects. And then you would pass this mSyncTargets object to OnlineDataSyncManager.start(...), so that the sync-manager works on this third object, not directly on the other two. This mSyncTargets object would pass the synchronization calls on to the nested actual sync-targets, one after the other.
The implementation of SyncTargetContainer would look something like this:
| public class SyncTargetContainer : IOnlineSyncTarget | | { | | private int mSendStepIndex; | | private int mReceiveStepIndex; | | private TopScoreListContainer mScoreContainer; | | private UserLevelData mUserLevels; | | | | public SyncTargetContainer(TopScoreListContainer scoreContainer, UserLevelData userLevels) | | { | | mScoreContainer = scoreContainer; | | mUserLevels = userLevels; | | } | | | | public void startSynchronization() | | { | | mSendStepIndex = 0; | | mReceiveStepIndex = 0; | | mScoreContainer.startSynchronization(); | | mUserLevels.startSynchronization(); | | } | | | | public void endSynchronization() | | { | | mScoreContainer.endSynchronization(); | | mUserLevels.endSynchronization(); | | } | | | | public void prepareForSending() | | { | | mScoreContainer.prepareForSending(); | | mUserLevels.prepareForSending(); | | } | | | | public bool writeTransferRecord(PacketWriter writer) | | { | | if (mSendStepIndex == 0) { | | if (mScoreContainer.writeTransferRecord(writer)) | | mSendStepIndex++; | | return false; | | } | | else | | return mUserLevels.writeTransferRecord(writer); | | } | | | | public bool readTransferRecord(PacketReader reader) | | { | | if (mReceiveStepIndex == 0) { | | if (mScoreContainer.readTransferRecord(reader)) | | mReceiveStepIndex++; | | return false; | | } | | else | | return mUserLevels.readTransferRecord(reader); | | } | | } | | | An implementation like this will effectively "chain" the two IOnlineSyncTargets together so that from the outside, they act as one sync-target. Of course this can easily be expanded to even more than just two sync-targets. And of course you may have to straighten out a bug or two in this quick-and-dirty code I wrote above. It was meant as an illustration only, not as a ready-to-run code sample!
Doc
|
|
-
|
|
Re: Global scoreboard component, by Spyn Doctor
|
Okay, I've been busy for a while because I thought I solved the problem. But, appearantly not. I can't get the highscores to show on either computer or xbox. They seem to send over but not recieve, I think.
So, here's a breakdown of the code. After the "Choose the active player" screen, I start the highscore manager. The player plays his game, and when he loses his final life. I add a new entry, save the highscore list, and then swap to the high score screen. There, the high score screen should show the other PC's high scores, but it doesn't. Neither does the PC show the Xbox's lists. Excuse the X and Y coordinates, that's from a debug.writeline.
UNKNOWN: MANAGER ENABLED
UNKNOWN: Looking for server
SystemLink
'IceBreaker.exe' (Managed (v4.0.30319)): Loaded 'C:\Windows\Microsoft.Net\assembly\GAC_MSIL\Microsoft.Xna.Framework.Storage\v4.0_4.0.0.0__842cf8be1de50553\Microsoft.Xna.Framework.Storage.dll'
UNKNOWN: Starting session
SERVER: Server time to live: 259.729sec
{X:-0.7071068 Y:-0.7071067}
{X:-0.7071068 Y:-0.7071067}
{X:-0.7071068 Y:-0.7071067}
{X:-0.7071068 Y:-0.7071067}
{X:-0.8660254 Y:-0.5}
SERVER: Client has connected, starting transfer
SERVER, RECEIVE: Voice disabled
End marker received
SERVER: Excluding server Dual Op Amp for the next 1800sec
SERVER: Receiving completed, switching to send mode
SERVER, SEND: Voice disabled
List 0 complete
Starting with list 1
List 1 complete
Starting with list 2
List 2 complete
Starting with list 3
List 3 complete
Starting with list 4
List 4 complete
Starting with list 5
List 5 complete
Starting with list 6
List 6 complete
Starting with list 7
List 7 complete
Starting with list 8
List 8 complete
Starting with list 9
List 9 complete
All lists complete
SERVER: Sending completed, waiting for end from client
SERVER: Client ended session, ending session on server side
SERVER: Session disposed
UNKNOWN: Looking for server
SystemLink
UNKNOWN: Starting session
SERVER: Server time to live: 283.636sec
SERVER: Server timed out
SERVER: Session disposed
UNKNOWN: Looking for server
SystemLink
UNKNOWN: Server found: Dual Op Amp
UNKNOWN: Server skipped as excluded: Dual Op Amp
UNKNOWN: Starting session
SERVER: Server time to live: 294.265sec
{X:-0.7071068 Y:-0.7071067}
{X:-0.7071068 Y:-0.7071067}
{X:-0.7071068 Y:-0.7071067}
{X:0.3794561 Y:-0.9252098}
{X:0.3794561 Y:-0.9252098}
{X:-0.7071068 Y:-0.7071067}
{X:-0.7071068 Y:-0.7071067}
{X:-0.7071068 Y:-0.7071067}
{X:-0.7071068 Y:-0.7071067}
{X:-0.7071068 Y:-0.7071067}
{X:-0.7071068 Y:-0.7071067}
{X:-0.7071068 Y:-0.7071067}
SERVER: Server timed out
SERVER: Session disposed
UNKNOWN: Looking for server
SystemLink
UNKNOWN: Server found: Dual Op Amp
UNKNOWN: Server skipped as excluded: Dual Op Amp
UNKNOWN: Starting session
SERVER: Server time to live: 389.274sec
The program '[4240] IceBreaker.exe: Managed (v4.0.30319)' has exited with code 0 (0x0).
Alright, this is the PC output, known as "TESTER2". That's just a local windows live account I made. I'm connected to my Xbox in the living room known as "Dual Op Amp". That's my CC account, well duh.
Anyway, it seems like the TESTER2 sends the data, but doesn't recieve it.
Here's the Xbox version.
SERVER: Client has connected, starting transfer
SERVER, RECEIVE: Voice disabled
{X:0.3794561 Y:-0.9252098}
{X:0.3794561 Y:-0.9252098}
{X:0.8660253 Y:-0.5000002}
{X:0.8660253 Y:-0.5000002}
{X:0.8660253 Y:-0.5000002}
End marker received
SERVER: Excluding server Tester2 for the next 1800sec
SERVER: Receiving completed, switching to send mode
SERVER, SEND: Voice disabled
List 0 complete
Starting with list 1
List 1 complete
Starting with list 2
List 2 complete
Starting with list 3
List 3 complete
Starting with list 4
List 4 complete
Starting with list 5
List 5 complete
Starting with list 6
List 6 complete
Starting with list 7
List 7 complete
Starting with list 8
List 8 complete
Starting with list 9
List 9 complete
All lists complete
SERVER: Sending completed, waiting for end from client
SERVER: Client ended session, ending session on server side
SERVER: Session disposed
UNKNOWN: Looking for server
SystemLink
{X:0.8660253 Y:-0.5000002}
{X:0.8660253 Y:-0.5000002}
UNKNOWN: Starting session
SERVER: Server time to live: 398.775sec
{X:-0.7071068 Y:-0.7071067}
{X:-0.7071068 Y:-0.7071067}
{X:-0.7071068 Y:-0.7071067}
{X:-0.7071068 Y:-0.7071067}
{X:-0.7071068 Y:-0.7071067}
{X:0.7071066 Y:-0.7071069}
{X:0.7071066 Y:-0.7071069}
{X:0.3794561 Y:-0.9252098}
{X:0.3794561 Y:-0.9252098}
{X:0.3794561 Y:-0.9252098}
{X:0.3794561 Y:-0.9252098}
{X:0.3794561 Y:-0.9252098}
{X:0.7071066 Y:-0.7071069}
{X:0.7071066 Y:-0.7071069}
{X:0.3794561 Y:-0.9252098}
{X:0.3794561 Y:-0.9252098}
{X:-0.7071068 Y:-0.7071067}
{X:-0.7071068 Y:-0.7071067}
{X:-0.7071068 Y:-0.7071067}
{X:-0.7071068 Y:-0.7071067}
{X:-0.7071068 Y:-0.7071067}
{X:-0.7071068 Y:-0.7071067}
{X:-0.7071068 Y:-0.7071067}
{X:-0.8660254 Y:-0.5}
{X:-0.8660254 Y:-0.5}
{X:0.7071066 Y:-0.7071069}
{X:0.7071066 Y:-0.7071069}
{X:0.3794561 Y:-0.9252098}
{X:0.3794561 Y:-0.9252098}
{X:0.3794561 Y:-0.9252098}
{X:0.3794561 Y:-0.9252098}
{X:0.3794561 Y:-0.9252098}
{X:-0.7071068 Y:-0.7071067}
{X:-0.7071068 Y:-0.7071067}
{X:-0.7071068 Y:-0.7071067}
{X:0.7071066 Y:-0.7071069}
{X:0.7071066 Y:-0.7071069}
{X:0.7071066 Y:-0.7071069}
{X:0.7071066 Y:-0.7071069}
{X:0.7071066 Y:-0.7071069}
{X:0.7071066 Y:-0.7071069}
{X:0.7071066 Y:-0.7071069}
{X:0.7071066 Y:-0.7071069}
{X:0.7071066 Y:-0.7071069}
{X:0.7071066 Y:-0.7071069}
|
|
-
-
- (8145)
-
Member
-
Posts
1,763
|
Re: Global scoreboard component, by Spyn Doctor
|
This log output shows, that the two games do indeed find each other, and communicate with each other, as designed. However, no data is being exchanged. Because if there were data exchanged, you would see it in the log too. That is, you would see it in the log of the Windows side: For every record sent, you would see a "*", for every send-record skipped (because the other partner already "knows" this record) you would see a "~" and for every record received you would see a "o". (See the code in TopScoreListContainer.readTransferRecord and TopScoreList.writeNextTransferEntry, where this debug output is made, but conditionally only "#if WINDOWS"). The most likely reason for this (i.e. that no data is being transfered) is, that the lists are indeed empty at the time of the score exchange. I recommend that you check the code where you add a new score to the list. Set a breakpoint into TopScoreListContainer.addEntry, to check if this method is indeed called when you think it is called. If it actually is called, then step through the code to see, if the entry is indeed added to the list, and if the call to OnlineDataSyncManager.notifyAboutNewLocalEntry is indeed executed. I suspect that this is where your problem lies.
Doc
|
|
-
|
|
Re: Global scoreboard component, by Spyn Doctor
|
Well, I just put a breakpoint there and it WAS executed. But, the data was not sent over.
Edit: I just did a lot of breakpoint debugging, and I found out that when I disable my "load highscore" code, it works. Strange, that has nothing to do with the code being sent over...
Any help?
|
|
-
-
- (8145)
-
Member
-
Posts
1,763
|
Re: Global scoreboard component, by Spyn Doctor
|
Well, it seems then that the problem is not within my component, but in the way how you integrate it into your game. I can point you to the readme.txt that comes with the component. Please make sure that you have read it and are following the instructions. Other than that, I can only give you general hints of where to look: How do you load the highscores? When do you do this (before starting the score exchange)? Could it be that by loading the highscores, you are creating a new instance of the TopScoreListContainer, so that you then have two instances: One into which the scores were loaded, and another (empty one) that is used by the OnlineDataSyncManager?
Doc
|
|
-
|
|
Re: Global scoreboard component, by Spyn Doctor
|
Well, that solved it. I placed the LoadScore() method after I started the Manager. Which caused the Manager to have a different list. It's now working perfectly.
Thanks for your help, man. You'll be in the credits.
|
|
-
|
|
Re: Global scoreboard component, by Spyn Doctor
|
Just wanted to say thanks again for this component. I'm using it to share game data (not just high scores), and it really appears to be working well.
Quick question: Is there any need for us, as developers, to do any sort of encryption or security? Is there a way people could cheat games that use your component to insert their own fraudulent high scores into the system?
I would assume that's something that is taken care of "under the hood"...
Adman
|
|
-
-
- (8145)
-
Member
-
Posts
1,763
|
Re: Global scoreboard component, by Spyn Doctor
|
As far as I know, the network traffic on Xbox LIVE is encrypted. So cheating by inspecting/changing network packets should be very, very difficult. The save data of your game on the storage device however is not very protected. I've never done this myself, but it is my understanding, that with the correct tools, it is trivial to connect an Xbox storage device to a PC and read/change the data on it. Actually, early after the release of YDAB!, I suspected that someone had tried to hack himself into the highscore list, by changing the data on his device (so that the changed data is then shared via my component). Even though this was only a suspicion, and I never had proof, I added a basic encryption/checksum feature to the save data, so that it was no longer possible to change it simply with a hex editor. However, that is not part of my score exchange component, but was something that I added to the storage side in my game only. So if you are concerned about this, you may want to add something similar.
Doc
|
|
-
|
|
Re: Global scoreboard component, by Spyn Doctor
|
Spyn Doctor:Even though this was only a suspicion, and I never had proof, I added a basic encryption/checksum feature to the save data, so that it was no longer possible to change it simply with a hex editor. However, that is not part of my score exchange component, but was something that I added to the storage side in my game only. So if you are concerned about this, you may want to add something similar.
When I initially read this, I wasn't too concerned and I didn't listen to this advice. But I've learned my lesson, now that someone has hacked their way to the top of my score list.
*sigh* What a pain.
|
|
|