A Flash Developer Resource Site

Results 1 to 18 of 18

Thread: socket event triggers movieclip movement, need more frequent rendering

  1. #1
    Senior Member
    Join Date
    Apr 2004
    Location
    LA
    Posts
    349

    socket event triggers movieclip movement, need more frequent rendering

    I have a flash project that's implementing a simple two player game. I have added an enterFrame function to my main movie which listens for keyboard activity in order to move a cowboy gunslinger. Whenever the cowboy moves, I send a message over a socket to a server which dispatches the movement to the other connected client. This works pretty well, however, the movement messages tend to arrive in clumps which results in jumpy animation of my cowboy on my opponents screen and jumpy animation of his cowboy on my screen. To illustrate, i put a trace in my enterFrame function because I am imagining that this function will run roughly once each time my main movie renders the screen. I also put a trace statement in my function that responds to network traffic and moves the remote player's cowboy on my screen. Here's the output:

    Code:
    enter frame:4
    enter frame:4
    enter frame:4
    client movePlayer running
    client movePlayer running
    client movePlayer running
    client movePlayer running
    client movePlayer running
    enter frame:4
    enter frame:4
    enter frame:4
    enter frame:4
    enter frame:4
    enter frame:4
    client movePlayer running
    client movePlayer running
    client movePlayer running
    client movePlayer running
    client movePlayer running
    client movePlayer running
    enter frame:4
    enter frame:4
    enter frame:4
    enter frame:4
    As you can see, I'll get several messages in a row from the remote client which instruct me to move their cowboy ('client MovePlayer running') and they will all run in a row before the screen updates.

    I'm guessing I should be using something like updateAfterEvent but this method is only provided by Mouse, Timer, and Keyboard events.

    So a few questions:

    1) Can someone recommend a good approach to force a screen render each time an incoming movePlayer event arrives over the socket? It's important to note that my function for handling these events has no visibility to the original socket data event.

    2) Am I right in understanding that the enterFrame function of my main movie happens once each time my movie is rendered? Is there some more accurate event to which I could attach a trace message so that I better understand the relative frequency of the render events and the socket events?

    3) Does firing an updateAfterEvent call force onEnterFrame events to happen more frequently? I'm worried about destroying performance by inadvertently firing more enterFrames which would fire more socket events which would fire more enterFrames, etc., etc.

    Any help would be much appreciated.
    Write multiplayer games with FlashMOG 0.3.1
    Try the MyPlan Salary Calculator

  2. #2
    Senior Member joshstrike's Avatar
    Join Date
    Jan 2001
    Location
    Alhama de Granada, España
    Posts
    1,136
    I've always approached multi-player games with pings and I've never written a socket server interface, so I can only advise with what I know from the world of turn-based gaming...

    What appears to be happening is that all your enterFrames are firing and then your data is being processed. There's no way to really stagger it so that enterFrames don't fire all at the same time. You could consider setting a Timer to run very very fast and update all the info incrementally as it comes in, if you need a whole state to change at the same time rather than piece by piece... but the best approach would probably be to put a listener on the socket that modified the MC's only when they needed to be modified.

    I almost never use enterFrame listeners because they really deteriorate performance...the only exception is for render loops, and even then I'll shut down the listener as soon as an object is holding still for a moment.

    For me, it's really critical to number the states passed back from the server, in case they show up in the wrong order. At that point, I always take the highest (latest) state and run the necessary animation to get the object to the new position. In that way I've even made countdown timers that ping every second and are exact enough on different machines to run out within about 0.1 seconds of each other; the only drawback is that on slow connections that miss a ping, they might "hiccup" slightly to resync themselves. That would be less of a problem with a socket.

    I never want to rely on every single piece of server data to send the object hither and yon. Just take the most recent piece of info, divide up the component attributes (x, y, scale, currentFrame, value, whatever...) and then write a class that has override setters for all those attributes that do a subtle tween on each one, rather than jump it directly to a position. I can't say for certain that this will work with your live action game, it might just be too slow or glitchy... but will certainly look a lot smoother and less jumpy than if you are relying on the push, because the character will still be executing the prior tween until the new tween takes its place (remember to always stop() the old tween before re-constructing it in the setter)...

    Not sure if this will help, but maybe it'll give you a useful view from the slow side...

  3. #3
    Senior Member
    Join Date
    Apr 2004
    Location
    LA
    Posts
    349
    Thanks for your response!

    My code is careful only to send a socket message when the cowboy has actually moved or his frame has changed (e.g., when he's firing his gun). In other words, I don't bother updating my cowboy on the remote client if he hasn't changed either position or frame. Here's the code responsible for sending my cowboy's position/frame info to the remote client:
    PHP Code:
        // move this cowboy on the remote clients BUT ONLY IF SOMETHING HAS CHANGED
        
    if (localCowboy.walking || (newFrame != localCowboy.priorFrame)) {
            
    gameService.server.movePlayer(gameInfo.keygameInfo.playerNumbernewXnewYnewFrame);
        } 
    So far I've had no problems with position updates running out of order. I believe this is because my code packs up instructions into a buffer and sends them out in bursts of bytes which arrive in order thanks to the TCP protocol. Given that the TCP protocol is reliable, I can be sure my messages arrive and I can be sure they arrive in order.

    I can appreciate your tweening suggestion. It did occur to me to do something similar and I may yet work on something like that. I think doing the immediate update is the right thing, however, because if one does not then there is a good chance the two player's screens will get out of sync and the positions seen by one player might be quite different than those seen by the other player.
    Write multiplayer games with FlashMOG 0.3.1
    Try the MyPlan Salary Calculator

  4. #4
    Senior Member joshstrike's Avatar
    Join Date
    Jan 2001
    Location
    Alhama de Granada, España
    Posts
    1,136
    Oh DUDE! You're the FlashMog guy!!! I really dig your work.
    When I was setting up my last multiplayer project I really wanted to try your server, I think it's an awesome idea, but I needed something that would run under PHP4, and under time pressure I had to give up trying to hack your code and go with the familiar AMFPHP ping method. I've really started to wish I'd used it. Next multiplayer game I do, I'm gonna give it a try...

  5. #5
    Senior Member
    Join Date
    Apr 2004
    Location
    LA
    Posts
    349
    Wow thanks. It's a bit embarrassing to be asking questions like this when I pretend at the same time to be some kind of expert, but I'm more of a PHP guy than an actionscript one.

    FYI, It might not be too hard to get FlashMOG working with PHP 4. You'd just have to go into all the classes and remove public/private/protected before each function and change variable declarations to use var instead of public/private/protected. I'm not certain about that but it's worth a try. I don't think there's anything else in version 0.2.4 that needs PHP5. I've been working on release 0.3 which is a pretty big improvement. At the moment I'm working on the 2-d shooter tutorial. I don't see any insurmountable obstacles but I want it to be fairly slick.
    Write multiplayer games with FlashMOG 0.3.1
    Try the MyPlan Salary Calculator

  6. #6
    Senior Member
    Join Date
    Apr 2004
    Location
    LA
    Posts
    349
    So I tried dispatching a TimerEvent when the network commands arrive. It doesn't seem to force the enterFrame function when I trigger this event, and the animation still seems a bit jumpy. For all I know, I could in fact be forcing flash to render the entire movie each time an incoming socket message arrives, but they are still arriving in clumps. I'd definitely like to know if I am forcing a render by doing this.

    The bottom line is that the movement's a bit jumpy and certain critical frames (e.g., when the cowboy is shooting) do not appear. I think your tweening approach may be necessary after all if I am to avoid that jumpy lag thing.
    Write multiplayer games with FlashMOG 0.3.1
    Try the MyPlan Salary Calculator

  7. #7
    Senior Member joshstrike's Avatar
    Join Date
    Jan 2001
    Location
    Alhama de Granada, España
    Posts
    1,136
    enabling Show Redraw Regions in the IDE player or in a debug version of flash player in the browser (right click) will show you what Flash is actually rendering...

    I didn't mean to say dispatch a TimerEvent, but rather, use a Timer to handle all your motion by collecting incoming calls from MOG, pushing them into an array, and then doing all the cached calls whenever the Timer fires. My thought was that it might help distribute the receipt functions more evenly in the milliseconds between screen redraws that way, instead of trying to execute them all on the enterFrame, which is exactly when Flash is trying to re-render everything. The reason is that the Timer is one of the few things that can be relied on to execute repeatedly more often than the framerate. If you set it to go every 10ms, it will try its best to do so...

    I think the problem I had with going to PHP4 with MOG had to do with some try/catch blocks and other stuff... I don't really remember... but I'm looking forward to giving the new version a spin.

  8. #8
    Senior Member
    Join Date
    Apr 2004
    Location
    LA
    Posts
    349
    I appreciate your thought on this. I think your advice is good.

    Enabled show Redraw Regions -- definitely a neat trick. However, it doesn't really give me a clear idea of precisely *when* the redraw is happening. My movie is currently running at 30fps and all the stuff that's supposed to be redrawing is indeed being redrawn. The problem is that the critical frame frequently gets dropped in which my shooter is firing...a bullet just erupts from a guy with his thumbs in his holster belt. I might set up a timer to keep my shooters in this stance which will likely improve things. I'm not sure what to do about the jumpiness.

    I'm not certain, but I think that when I dispatch a TimerEvent from my main timeline and its listener calls updateAfterEvent, then that will force the whole movie to redraw immediately. Check out this function which receives a request from the server when a remote player has fired. I have altered it so that it dispatches a TimerEvent whenever a player movement order is received that actually moves the player:
    PHP Code:
    // this routine allows the server to move players within this client
    gameService.client.movePlayer = function(playerNumber:intnewX:intnewY:intnewFrame:String):void {
        var 
    player:MovieClip players[playerNumber];
        if (!
    player) {
            
    trace('PLAYER ' playerNumber ' does not exist!');
            return;
        }
        var 
    updateAfter:Boolean false;
        
        if (
    player.!= newX) {
            
    player.newX;
            
    updateAfter true;
        }
        if (
    player.!= newY) {
            
    player.newY;
            
    updateAfter true;
        }

        if ((
    newFrame != null) && (newFrame != player.priorFrame)) {
            
    updateAfter true;
            
    player.priorFrame newFrame;
            if (
    newFrame == 'standing') {
                
    player.gotoAndStop(newFrame);
            } else {
                
    player.gotoAndPlay(newFrame);
            }
        }
        
        if (
    updateAfter) {
            var 
    tevt:TimerEvent = new TimerEvent(TimerEvent.TIMERfalsefalse);
            
    dispatchEvent(tevt);
        }
    // gameService.client.movePlayer() 
    I then have a super simple event listener waiting for a timer event which does in fact get fired, apparently without complaint:
    PHP Code:
    function updateFunc(tevt:TimerEvent) {
        
    tevt.updateAfterEvent();
    }
    addEventListener(TimerEvent.TIMERupdateFunc); 
    I see this function firing repeatedly and I assume it works as there is no complaint from the compiler or during code execution. I believe that it may in fact force a movie redraw when calling updateAfterEvent. The problem is that it might fire redraws in such rapid succession that one never sees the distinct frames...just the last one in a cluster of commands.

    I believe this jumpiness is rleated to the network commands arriving in clusters. I think the ability to influence this behavior of socket commands arriving in clusters would require some deeper-level programming that would influence the behavior of a socket's DATA event which is beyond the scope of Actionscript coding. I could be wrong.

    I'll maybe try your approach where I use a timer instead of enterFrame and this may help with the clustering of network messages. Of course, at 30 fps, enterFrame should fire roughly every 33 milliseconds which is pretty close.

    RE: FlashMOG...forgot about those try/catch blocks. Also "throw new Exception". There are about 12 try/catch blocks in the latest version (not yet published) and they are all in FlashMOGPolicyServer.php (a new file) and FlashMOGServer.php. IT wouldn't be too hard to root those out.

    the real problem is the "throw new exception" stuff. There are a lot more of those. They could probably just be replaced with a die('error blah blaah'). You shouldn't be getting exceptions in your code so if you are, then dying is an appropriate response.
    Write multiplayer games with FlashMOG 0.3.1
    Try the MyPlan Salary Calculator

  9. #9
    Senior Member joshstrike's Avatar
    Join Date
    Jan 2001
    Location
    Alhama de Granada, España
    Posts
    1,136
    Here's kind of a shorthand way of how I'd go about it... I think some of your problem may have to do with the way you're setting the frames in the movieclips, and the Timer might not even be necessary, but this might help...

    Code:
    // this routine allows the server to move players within this client
    
    public var updateFrequence:int = 10; //milliseconds
    
    public var timer:Timer = new Timer(updateFrequency);
    timer.addEventListener(TimerEvent.TIMER,runBatch)
    timer.start();
    
    public var batch:Array = [];
    
    gameService.client.movePlayer = function(playerNumber:int, newX:int, newY:int, newFrame:String):void {
        var player:MovieClip = players[playerNumber];
        if (!player) {
            trace('PLAYER ' + playerNumber + ' does not exist!');
            return;
        }
        
        if (player.x != newX || player.y != newY) {
           batch.push([playerNumber,newX,newY,newFrame]);
        }
    
    } // gameService.client.movePlayer() 
    
    function runBatch(evt:TimerEvent):void {
    	for each (var p:Array in batch) {
    		updateFunc(p);
    	}
    	batch = [];
    }
    
    function updateFunc(p:Array):void {
    	with (players[p[0]]) {
    		x = p[1];
    		y = p[2];
    		newFrame = p[3]; //with a setter on the players class to make it do this
    	}
    }

  10. #10
    Senior Member
    Join Date
    Apr 2004
    Location
    LA
    Posts
    349
    That's a pretty brief/snappy solution, but I think the problem lies at a level below actionscript, you know what I mean? The messages arrive in clumps so that calls to gameService.client.movePlayer basically all happen at the same time. In this case, runBatch would have basically the same effect as my current code because each batch would have several movements for the same player.

    Behold...I put a trace in gameService.client.movePlayer and moved a single player around. Notice the clustering of timestamps (which are in seconds):
    Code:
    client.movePlayer running at 1242147483.578
    client.movePlayer running at 1242147483.578
    client.movePlayer running at 1242147483.578
    client.movePlayer running at 1242147483.578
    client.movePlayer running at 1242147483.859
    client.movePlayer running at 1242147483.859
    client.movePlayer running at 1242147483.859
    client.movePlayer running at 1242147483.859
    client.movePlayer running at 1242147483.875
    client.movePlayer running at 1242147483.875
    client.movePlayer running at 1242147483.875
    client.movePlayer running at 1242147483.875
    What should be a dozen movements of a player now appears as only 3. I think you will agree that your runBatch suggestion will not solve this problem. It *might* get half of the .578 in one batch and half in another but it's very unlikely given they all occur in the same millisecond.

    As I was trying to explain before, the problem seems to lie at some level beneath actionscript somewhere in the flash player's network handling code.
    Write multiplayer games with FlashMOG 0.3.1
    Try the MyPlan Salary Calculator

  11. #11
    Senior Member joshstrike's Avatar
    Join Date
    Jan 2001
    Location
    Alhama de Granada, España
    Posts
    1,136
    Can you compare that with a trace of when exactly the XMLSocket.onData is received? Are they clustered as well, or is something happening after that?
    I noticed when you call the MethodFunc in RPCSocket (I'm looking at the old version, forgive me) you're running an apply() with null as the thisObject -- meaning the service functions may not work properly in the scope of their own classes. Long shot here, but if you see that the onData are received much more frequently, and your functions are getting called in a clumped-up way, you might want to try putting the service functions into the scope of their home objects, or even of the RPCSocket for expediency. I'm not sure what the VM does when it encounters an apply() sent against a null thisObject, but such a thing is usually intended for static functions (someone might correct me if I'm wrong about that), not object methods...

    It's also totally possible that XMLSocket.onData is only firing on every frame and is somehow caching the serial strings up until that point. That one's over my pay grade...

  12. #12
    Senior Member
    Join Date
    Apr 2004
    Location
    LA
    Posts
    349
    OK so I put a trace call as the first line in my socketDataHandler function. This is the routine added as an event listener to the socket's SOCKET_DATA event. There is also a trace in the movePlayer method which gets invoked by the socketDataHandler function. The trace output tells me that when socket data arrives and the socket's ProgressEvent.SOCKET_DATA event is dispatched, the socket's buffer usually contains several instructions -- EVEN THOUGH these instructions were dispatched separately by the server:
    Code:
    * socketDataHandler running at 1242153515.5
    client.movePlayer running at 1242153515.515
    * socketDataHandler running at 1242153515.703
    client.movePlayer running at 1242153515.703
    client.movePlayer running at 1242153515.703
    client.movePlayer running at 1242153515.703
    client.movePlayer running at 1242153515.703
    client.movePlayer running at 1242153515.703
    * socketDataHandler running at 1242153515.89
    client.movePlayer running at 1242153515.89
    client.movePlayer running at 1242153515.906
    client.movePlayer running at 1242153515.906
    client.movePlayer running at 1242153515.906
    client.movePlayer running at 1242153515.906
    client.movePlayer running at 1242153515.906
    * socketDataHandler running at 1242153516.093
    client.movePlayer running at 1242153516.109
    client.movePlayer running at 1242153516.109
    client.movePlayer running at 1242153516.109
    client.movePlayer running at 1242153516.109
    client.movePlayer running at 1242153516.109
    The timing of the socket data events appears to be every 150-200 milliseconds which corresponds to once every 5-7 frames.

    I tried using apply with a service object as thisObject:
    PHP Code:
    methodFunc.apply(services[serviceName], args); 
    but that doesn't change the clumping behavior at all. It's been awhile since I looked at that code, but I believe the reason I chose null was because I was unable to decide which object should be passed as thisObject. I can't imagine the use of null for this value would result in delays of over 100 milliseconds.
    Write multiplayer games with FlashMOG 0.3.1
    Try the MyPlan Salary Calculator

  13. #13
    Senior Member joshstrike's Avatar
    Join Date
    Jan 2001
    Location
    Alhama de Granada, España
    Posts
    1,136
    This starts to sound like something with the event flow coming off of the DATA listener...

    One thing you could try is modifying the priority of the event listener and using the capture phase... instead of

    socket.addEventListener(XMLSocket.DATA,onData);

    try:

    var priority:int = 10000;
    socket.addEventListener(XMLSocket.DATA,onData,true ,priority);
    (edit) priority--; //this should grant top priority to the first call coming in.

    Even just using the priority, leaving capture phase to the default false, might help...

  14. #14
    Senior Member joshstrike's Avatar
    Join Date
    Jan 2001
    Location
    Alhama de Granada, España
    Posts
    1,136
    If it's not that, then it really is something with the XMLSocket...

  15. #15
    Senior Member
    Join Date
    Apr 2004
    Location
    LA
    Posts
    349
    In the latest version of flashMOG, I'm using a regular old Socket rather than XMLSocket - the reason being that XMLSocket requires null chars as a delimiter which might cause problems if your data contains null chars.

    I tried adding the listener for capture phase of SOCKET_DATA and it never fired. I switched back to capture = false:
    PHP Code:
                addEventListener(ProgressEvent.SOCKET_DATAsocketDataHandlerfalse10000); 
    all other event listeners in my code have no priority specified (meaning it defaults to zero). This had no impact on the message clumpiness problem.

    These are good suggestions, though. thanks.
    Write multiplayer games with FlashMOG 0.3.1
    Try the MyPlan Salary Calculator

  16. #16
    Senior Member
    Join Date
    Apr 2004
    Location
    LA
    Posts
    349
    PROBLEM SOLVED YEAH. It was a server-side issue relating to Nagle's algorithm. I fixed the problem by setting an additional socket option. Now I'm getting awesomely prompt position updates and very smooth movement. It's worth noting that there is usually a hiccup at the beginning but then everything is smooth sailing.
    Code:
    socketDataHandler running at 1242238803.234
    client.movePlayer running at 1242238803234
    client.movePlayer running at 1242238803250
    client.movePlayer running at 1242238803250
    client.movePlayer running at 1242238803250
    client.movePlayer running at 1242238803250
    client.movePlayer running at 1242238803250
    client.movePlayer running at 1242238803250
    client.movePlayer running at 1242238803265
    socketDataHandler running at 1242238803.312
    client.movePlayer running at 1242238803312
    socketDataHandler running at 1242238803.343
    client.movePlayer running at 1242238803343
    socketDataHandler running at 1242238803.375
    client.movePlayer running at 1242238803375
    socketDataHandler running at 1242238803.406
    client.movePlayer running at 1242238803406
    socketDataHandler running at 1242238803.437
    client.movePlayer running at 1242238803453
    socketDataHandler running at 1242238803.468
    client.movePlayer running at 1242238803484
    socketDataHandler running at 1242238803.5
    client.movePlayer running at 1242238803515
    socketDataHandler running at 1242238803.546
    client.movePlayer running at 1242238803546
    socketDataHandler running at 1242238803.578
    client.movePlayer running at 1242238803578
    BAng! position updates every 30 milliseconds or so. Pretty awesome.
    Write multiplayer games with FlashMOG 0.3.1
    Try the MyPlan Salary Calculator

  17. #17
    Senior Member joshstrike's Avatar
    Join Date
    Jan 2001
    Location
    Alhama de Granada, España
    Posts
    1,136
    Awesome! So basically you're alternating the downstream traffic between two sockets to force the network not to queue it?

  18. #18
    Senior Member
    Join Date
    Apr 2004
    Location
    LA
    Posts
    349
    Nope. It's still just one socket connection, I just set one more option that I had not bothered to set before which instructs the OS to send traffic instantly no matter how small the message is (option is TCP_NODELAY). Sorry if I worded that funny before.

    It looks so much better now. Really fast, smooth movement. Schweet.
    Write multiplayer games with FlashMOG 0.3.1
    Try the MyPlan Salary Calculator

Tags for this Thread

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •  




Click Here to Expand Forum to Full Width

HTML5 Development Center