GitHub | Documentation | Discord (chat) | Become a Patron

Stencyl (HaXe) Extension



  • This week I've started using Colyseus. I'm going to try to create a HaXe Extension for Stencyl so that I can publish multiplayer games with Colyseus.
    Previously I've created a couple of multiplayer extensions for Stencyl but failed to get anything near realtime. Maybe Colyseus can help?!?
    From first glance the Colyseus stuff looks great. I love the fact that I can run the server on my own infrastructure.

    It will be a long road ahead and I've decided to start this Showcase thread to let you all follow my progress. Maybe you can help me along the way?!?

    First I started by porting the example nyancat to a Stencyl game (Using a bitmap for the cat)

    Flash publication failed! I've tried to put the flash (swf) on the same server and experimented with crossdomain.xml. But apparently websockets do not care about that.

    Working clients:

    • HTML5 (Tested only Chrome)
    • Windows native
    • Mac OSX native
    • iOS (iPad mini)
    • iOS Simulator (iPhone 5s)
    • Android (Samsung S3 Note)
    • Linux


  • Since I like developers journals myself, I'm writing one for the Stencyl Colyseus Extension journey.

    The main idea of the Stencyl Extension is a general purpose Server with accommodating Stencyl blocks.
    Not sure if that is the right approach, but that is the path I take now.

    Goals:

    • Application ID system
    • Lobby system
    • Turn Based system
    • System that one client acts as a server for the other clients and itself
    • Chat system

    First, I want something persistent. Currently the thought process is that there is a LobbyRoom.
    That can be used for MatchMaking but I hope to use it for public storage of the application IDs.

    Persistence

    The journey leads to investigations on how to store application ids.
    This can be using a simple text file since I'm aiming for a single server (at this time) and I
    hopefully don't need a (shared) database server.

    Test LocalPresence : adjusted rooms/01-chat-room.ts

    import { LocalPresence } from "colyseus";
    

    after maxClients = 4;

    private presence:LocalPresence=new LocalPresence();
    

    Added to onMessage:

    var msg = this.presence.hget("MdotTest","Field");
    console.log("Last Message: ", msg);
    this.presence.hset("MdotTest", "Field", data.message);
    

    This works really well. When leaving room and joining again it still knew the last message.
    However, when I kill the server and start it up the data is lost.

    I couldn't find any writing to files in https://github.com/colyseus/colyseus/tree/master/src/presence

    In RedisPresence there is use of promisfy but no where is something with files (at least so far I can see)

    How do we do server reboot persistence?!

    This is what I hacked together and I'm sure there is something else that I should be doing so please point m.e. to the better thing!!!

    import { readFileSync,writeFileSync } from "fs";
    

    Added to onJoin:

    var jsonfile=JSON.parse(readFileSync('temp.txt','utf8'));
    var item=jsonfile.hash;
    //console.log('item', item.MdotTest.Field);
    this.presence.hset("MdotTest", "Field", item);
    

    Added to onDispose:

    writeFileSync("temp.txt", JSON.stringify(this.presence));
    

    Now at least the data is persistent even when there is a server boot/crash/shutdown (not when it crashes when writing ...)


  • administrator

    hey @mdotedot, not exactly sure what you're planning to build, but to persist the data across server reboots you can use the RedisPresence alternative. The LocalPresence really doesn't persist any data. Cheers!



  • Thanks Endel! It appears that RedisPresence needs a Redis Server!
    When using RedisPresence and you don't have a running Redis Server you get this:

    Could not connect to Redis at 127.0.0.1:6379: Connection refused
    

    For running a server this quick start is available : https://redis.io/topics/quickstart

    I hate to run different things on the server since I want to have deployment by the Extension users as easy as it can be.

    Maybe I will revisit this approach when I need persistence.
    I've changed the approach to ApplicationIDs and 'just' generate some IDs for the Extension Users.

    In the chat I've been told that one of my room-mechanism approach will fail. We will see where this thing is going to crash ...
    I'm not promising that I can create a general purpose server for all kinds of clients.
    If an approach don't work, extension users still have the ability to change the server themselves.
    Although they probably lack the skills, they can hire you guys to make the server for them :D
    There will be overhead on the proxy mechanism, but we will see what can be done.
    Rather than saying that running logic on client is impossible I still want to try it.
    Using the Colyseus server as a proxy to the active client.

    For now I've created a turn based mechanism on the server that I need to write some clients for.

    The example : 02-state-handler.ts appears to be working correctly with below additions, but I guess that I need to investigate
    different types of turn based games and their server-side logic.

    These are the steps taken to rotate player activity: (Modify 02-state-handler.ts)

    After : something = "This ...

    
            nextPlayer(){
                    var doNext=false;
                    for(var m in this.players){
                            if(doNext){
                                    this.players[m].ActivePlayer=m;
                                    doNext=false;
                            }else{
                                    if(this.players[m].ActivePlayer != ""){
                                            this.players[m].ActivePlayer="";
                                            doNext=true;
                                    }
                            }
                    } // check Next available player
                    if(doNext){ // if we need to shift but there are no players after : first player
                            for(var m in this.players){
                                    if(doNext){
                                            this.players[m].ActivePlayer=m;
                                            doNext=false;
                                    }
                            }// go through all players
                    } // check first available player
            }// ActivePlayer shift
    // Changed createPlayer
        createPlayer (id: string, room:Room) {
            this.players[ id ] = new Player();
            if(room.clients.length == 1){ // first player in room
                    this.players[id].ActivePlayer=id;
            }
        } // createPlayer
    
    

    In removePlayer we need to check if the leaving player is the active player and rotate when necessary:

        if(this.players[id].ActivePlayer!="")this.nextPlayer(); 
    

    movePlayer includes a check on activity: After movePlayer(id: string, movement: any){

       
            if(this.players[id].ActivePlayer != id)	return; // ignore move when not active
    		
    

    And before the end of the function:

    	this.nextPlayer(); // after move 
    

    Adjusted class Player with the ActivePlayer :

    
    export class Player {
        x = Math.floor(Math.random() * 400);
        y = Math.floor(Math.random() * 400);
            ActivePlayer = "";
    }
    
    

    Adjusted onJoin :

       this.state.createPlayer(client.sessionId, this);
    

    When running this example the players can only move when it is their turn.
    It is a basic example with no validation on move or if time expired etc..



  • This week I've tried to make a few Stencyl Extension blocks:

    • get Player (ID / Name)
    • get Room (ID / Name)
    • assign Name [..] to [room/player] with id []
    • List of Rooms
    • List of Players

    It took me a while to find out that client.id != player.id. It appears that player is instantiated with room.SessionId.

    Then it took me a lot longer to get a list of rooms (of the same type).
    getAvailableRooms seems to show a list of rooms where you can join to, not the ones that are full or something like that.
    I (also) want rooms with large number of possible clients, so the client is now always joining the same room where I want the client to be able to create a new room.

    Apparently we need to use the MatchMaker to get a list of rooms. But I fail to get it to use.
    It is a Server component, but how do I access this from the client when the client isn't in a room?

    I've also looked at the example on MatchMaker but it registered the rooms differently then I was seeing in the 02-state-change example
    Source: https://github.com/colyseus/colyseus/blob/master/src/MatchMaker.ts

    At last I've hacked something together that appears to be working for now.

    globals.ts:

    //
    // Global data for the server
    //
    export abstract class Globals{
            private static theRooms =  [];
    
            public static set(index:string,value:string){
                    this.theRooms[index]=value;
            }
    // get, remove as well when room gets disposed
    }
    

    This static class can hold 'server-wide' globals as long as you import them in your room-code, like:

    import { Globals } from "../globals"; // global data from the server
    
    //  onInit :
      Globals.set(""+this.roomId, "state_handler");
    
    

    This method allows me to get the list of rooms from other client sessions and even for other rooms when I want...

    I'm sure that there is another way to get this done using the MatchMaker but I couldn't find example of this.



  • Since a couple of days I'm making a mess of things :(

    I have a feeling that I'm doing this the wrong way.

    It all started with the 02-state-handler.ts

    What I conclude is that you have a client that has an ID and that is constant no matter what room is created or joined.
    However each joining or creation of a room is done with a session ID. SessionID != Client.ID

    I want the game(s) to be able to get a list of players in the room.

    export class RoomData{
        playernames = "";
        roomnames = "";
        roomids = "";
        sessionids = "";
    }
    
    export class Player {
    	ID = "";
    	Name = "";
        x = Math.floor(Math.random() * 400);
        y = Math.floor(Math.random() * 400);
        width = 132;
        height = 132;
    
    }
    

    Currently I use the RoomData class to alter the state of the server. These RoomData members are being broadcasted to the clients.
    It feels kind of hacky to set these variables to have the roomstate being synchronized.

    The way that I'm currently setting these values is in client (room.send) and server onMessage communications.

    This is the current client perspective:

    New Room:

    http://photoquesting.com/Colyseus/NewRoom.PNG

    Second Room:

    http://photoquesting.com/Colyseus/SecondRoom.PNG

    Joining From the other Room (looks like it works)

    http://photoquesting.com/Colyseus/JoinedRoom.PNG

    And second player joining the third (this fails : player seems to be in two rooms ??!? )

    http://photoquesting.com/Colyseus/ThirdRoom.PNG

    Server Side Data :

    
    roomName with id:  RoomName:3tAeV4aU7 the changeID is pQZlJiSNb  the ID without Name:  3tAeV4aU7  the name is  Room424
    roomName with id:  RoomName:MZOaBj7hu the changeID is pQZlJiSNb  the ID without Name:  MZOaBj7hu  the name is  Room6959
    roomName with id:  RoomName:pQZlJiSNb the changeID is pQZlJiSNb  the ID without Name:  pQZlJiSNb  the name is  Room7704
    roomnames:  3tAeV4aU7=Room424:MZOaBj7hu=Room6959:pQZlJiSNb=Room7704:
    playername with id:  PlayerName:iwIpOcjZs  the changeID is pQZlJiSNb  the id without name:  iwIpOcjZs  the name is  Player8707
    playername with id:  PlayerName:j6a-rk8dc  the changeID is pQZlJiSNb  the id without name:  j6a-rk8dc  the name is  Player4301
    playername with id:  PlayerName:EsP17dY-m  the changeID is pQZlJiSNb  the id without name:  EsP17dY-m  the name is  Player1374
    playernames:  iwIpOcjZs=Player8707:j6a-rk8dc=Player4301:EsP17dY-m=Player1374
    
    

    As it is fairly hacky stuff I'm hoping that someone has dealt with this before and can shed some light ....

    When I don't use Client.ID and only show SessionIDs it worked much better, but how can I get a client (name) from a session?
    The Player Class data does not appear to be part of a state. What should I do to get Player List with Names?!



  • Since I couldn't get HaXe Json to work (it doesn't know the format) I used Reflection to get the information I need:

    // Get The Player ID/Name
    			var thePlayers:Dynamic = Reflect.field(state, "players" );
    			var sessionIDs:Array<Dynamic> = Std.string(thePlayers).split(": {");
    			for(sessionID in sessionIDs){
    				var sessionid:String="";
    				if(sessionID.indexOf("{") == 0) sessionid=sessionID.split("{")[1]; // for the first player
    				if(sessionID.indexOf("},") > 0) sessionid=sessionID.split("},")[1]; // for all the rest
    				if(sessionid == null) sessionid=sessionID;
    
    				var onePlayer:Dynamic = Reflect.field(thePlayers, StringTools.trim(sessionid));
    	
    				var ID:String = Reflect.field(onePlayer, "ID" );
    				if(ID != null && ID.length > 0)	trace("playerlist One Player . ID : "+ID);
    				var Name:String = Reflect.field(onePlayer, "Name" );
    				if(Name != null && Name.length > 0) trace("playerlist OnePlayer . Name : " + Name);
    				
    			} // for all sessionIDs in the room
    			
    

    Now it can get the ID and Name from the Player Class !

    This seems a lot better than the previous approach.

    I can also now use the availableRooms construction since I use a method on the requestJoin where an option is given when joining a room as oposed to creating a new one...

    I might need to use the RoomData for the name of the Room. But maybe someone else has some opinions about it?!



  • Client debugging ... Leaving both current and joining room with just one room.leave() call ....

    HTML5 works well, Windows is doing well most of the time, but Mac and Android are having troubles in leaving and joining. I suspect that Windows problems are caused by the same issue, but couldn't steadily reproduce that.

    It is the same HaXe client-code due to being a HaXe extension.
    There are only differences in Player Name gathering on HTML versus native. Didn't test iOS/Linux.

    What I think is happening is that room.leave() is done twice on Mac/Android?!?
    What can be the cause of this?!

    HTML5 debug:

     Colyseus.hx:156:  JoinRoom to joinID: BWNT-kGd0  Calling Room Leave with room.id: NRL_w4txR
     Colyseus.hx:421: leavecounter: 1 room.onLeave RoomID: NRL_w4txR Session ID: XqE4dF-s6  
     Colyseus.hx:275: (Client: )p6_HgFPtE join room to JoinID : BWNT-kGd0
     
    Good: 
     Colyseus.hx:304:  joinRoom : joinCounter: 2  Joined Room BWNT-kGd0 sessionID : VXeeGm4oI
    

    Debug Mac:

     Colyseus.hx:156:  JoinRoom to joinID: z40eavIgr  Calling Room Leave with room.id: Ge_RICPir
     Colyseus.hx:421: leavecounter: 1 room.onLeave RoomID: Ge_RICPir Session ID: f2c1YeU3i  
     Colyseus.hx:275: (Client: )9EoXglrY9 join room to JoinID : z40eavIgr
     Colyseus.hx:421: leavecounter: 2 room.onLeave RoomID: z40eavIgr Session ID: null  
    

    Never reached joinRoom!!
    And it does TWO leave rooms !!!

    The output on server side is the same (of course not the IDs and names ...)

    HTML5

    Request join : this.roomId: NRL_w4txR  options.Type:  undefined PlayerName:  APlayer3642  OldMax:  1
    Number of clients in room:  1  the room id is :  NRL_w4txR
    Request join : this.roomId: NRL_w4txR  options.Type:  undefined PlayerName:  APlayer7523  OldMax:  100
    Request join : this.roomId: NRL_w4txR  options.Type:  undefined PlayerName:  APlayer7523  OldMax:  100
    StateHandlerRoom created! { PlayerName: 'APlayer7523',
      RoomName: 'Room5672',
      requestId: 2,
      clientId: '3VTAK8HPh',
      lobby: { rooms: [] } }
    Request join : this.roomId: BWNT-kGd0  options.Type:  undefined PlayerName:  APlayer7523  OldMax:  1
    Number of clients in room:  1  the room id is :  BWNT-kGd0
    Number of clients in room:  0  the room id is :  NRL_w4txR
    Dispose StateHandlerRoom :   remove room.id     :  NRL_w4txR
    Request join : this.roomId: BWNT-kGd0  options.Type:  JOIN PlayerName:  APlayer3642  OldMax:  100
    Number of clients in room:  2  the room id is :  BWNT-kGd0
    Number of clients in room:  1  the room id is :  BWNT-kGd0
    

    MAC:

    Request join : this.roomId: Ge_RICPir  options.Type:  undefined PlayerName:  APlayer7255  OldMax:  1
    Number of clients in room:  1  the room id is :  Ge_RICPir
    Request join : this.roomId: Ge_RICPir  options.Type:  undefined PlayerName:  Player5710  OldMax:  100
    Request join : this.roomId: Ge_RICPir  options.Type:  undefined PlayerName:  Player5710  OldMax:  100
    StateHandlerRoom created! { PlayerName: 'Player5710',
      RoomName: 'Room1021',
      requestId: 4,
      clientId: 'vJGYZCiJ5',
      lobby: { rooms: [] } }
    Request join : this.roomId: z40eavIgr  options.Type:  undefined PlayerName:  Player5710  OldMax:  1
    Number of clients in room:  1  the room id is :  z40eavIgr
    Number of clients in room:  0  the room id is :  Ge_RICPir
    Dispose StateHandlerRoom :   remove room.id     :  Ge_RICPir
    Request join : this.roomId: z40eavIgr  options.Type:  JOIN PlayerName:  APlayer7255  OldMax:  100
    Number of clients in room:  2  the room id is :  z40eavIgr
    Number of clients in room:  1  the room id is :  z40eavIgr
    

    I even used a timer to call join after the leave so that it does it 5 or 10 seconds later.
    As soon as I do the join the leave call is executed !!!



  • Colyseus : Leaving room

    Since a couple of weeks I'm debugging the leaving room situation. It is kind of driving m.e. nuts.

    Today I started over since I was not sure if my player information was ruining stuff or the leaving room situation.

    All seems well with HTML5. But the CPP builds (Windows and Android) are giving me problems.

    Most of the time the Windows build performs well, unless I try to do a Windows debug build. Then it crashes immediately.

    Running the Android version gives me almost immediately problems. (I estimate that it is doing leave room twice so the new room is left immediately)

    trace("input.readBytes catch : "+e);				
    if(Std.is(e,Error)) trace("isError"); // still OK
    /* Output:
    input.readBytes catch : Custom(EOF)
    isError 
    */
    // Below line crashes: debug build 
    //if( (e:Error).match(Error.Custom(Error.Blocked)) ) trace("ErrorCustom");  // Null Pointer Exception
     
     // And since it is used in the needClose detection it fails
                         needClose = !(e == 'Blocking' || (Std.is(e, Error) && (
                            (e:Error).match(Error.Custom(Error.Blocked)) ||
                            (e:Error).match(Error.Blocked))
                        ));
    

    So I cannot debug using Visual Studio...

    But I don't care if Windows Debugger works as long as the issue can be resolved without it ...

    This is the code that works:

    
      public static function joinRoom(ID:String){
    	  if(isInit){
    		var theOptions:Map<String, Dynamic>=new Map();
    		theOptions.set("Type", "JOIN");
    		theOptions.set("PlayerName", "TESTPLAYER" );
    		//if(room!=null) room.leave();				
    		room =  client.join(""+ID, theOptions);
    	  }
    	  
      }// joinRoom
      
      public static function createRoom(RoomType:String){
    	if(isInit){
    		var theOptions:Map<String, Dynamic>=new Map();
    		theOptions.set("Type", "NEW");
    		theOptions.set("PlayerName", "TESTPLAYER" );
    		//if(room!=null) room.leave();
    		room =  client.join(RoomType, theOptions);  
    	}
      }
      
    
    

    But when I comment out the room.leave so that it actually leaves to current room and then Android gives me problems and
    sometimes Windows crashes or gives the same problem (0 clients in room and then the room will be autodisposed)

    Any suggestions are appreciated!

    By the way : if I don't use room.leave and use the colyseus monitor to dispose them it works fine(!)

    Also, I tried using a timer behind the room.leave to allow the engine to dispose the room but it looks like leave is called when I initiated another room.

    I also tried using an Array of rooms where I leave the room[roomCounter] but this acts like it is also leaving the initiated room



  • Colyseus leaving room part 3

    There hasn't been made any client side communication yet and I have worked for over a month on this room leaving situation.
    I really hope this isn't the way that all the other stuff is going to turn out :(

    Sending 'LEAVE' to the server and let the server disconnect the client is also not reliable!

    Modified SocketSys.hx to avoid crash on windows:

    					// Do not use        (e:Error).match(Error.Custom(Error.Blocked)) ||
                         needClose = !(e == 'Blocking' || (Std.is(e, Error) && (
                            (e:Error).match(Error.Blocked))
                        ));
    					
    

    New direction : Plan for failure!
    Since I cannot do a steady leave room and since there can be other situations that lead to errors: I decided that the client/game needs to react on the errors.

    This led to changes to the Colyseus HaXe code since some errors (websocket and connection) aren't passed to Room.onError
    Connection.hx

    	// This does not throw onError on room .. this message cannot be detected
    	this.ws.onerror = function(message) {
                this.onError(message);  // this isn't caught in Room.onError !!!!
    			// Stencyl notification (import com.stencyl.behavior.Script.*;)
    			if(getGameAttribute("ColyseusErrorMessage") == null)setGameAttribute("ColyseusErrorMessage", ""); setGameAttribute("ColyseusErrorMessage", ""+getGameAttribute("ColyseusErrorMessage")+" : "+message); //M.E.
    
            }
    
    

    Unfortunately Mac OSX build still produces crash sometimes

    
    [My Game] 2019-02-25 00:36:22.937 My Game[28583:2109033] -[NSPersistentUIWindowSnapshotter writeWindowSnapshot:length:width:height:bytesPerRow:toFile:inDirectory:encryptingWithKey:uuid:checksum:fd:]: 0 == ftruncate(fd, finalFileSize) failed on line 797: Bad file descriptor
    [My Game] 2019-02-25 00:36:22.939 My Game[28583:2109033] -[NSPersistentUIWindowSnapshotter writeWindowSnapshot:length:width:height:bytesPerRow:toFile:inDirectory:encryptingWithKey:uuid:checksum:fd:]: 0 == ftruncate(fd, fileLength) failed on line 868: Bad file descriptor
    DEBUG [Thread-17] stencyl.sw.util.net.SocketServer: _disconnected: Socket[addr=/127.0.0.1,port=49807,localport=18525]
    
    

    As well as the Android build for which I am unable to get logs.
    But it happens less than before.

    There are also situations where there are rooms left with 0 clients in them, even when the application/game is killed. And even the Colyseus monitor

    Now it is time to work on the room information like a playerlist. Hopefully this does not take much time and doesn't interfere with the current workaround.

    Still, if anyone has any tips , experiences or possible things I can try relating to the (e:Error).match(Error.Custom(Error.Blocked)) ||
    or the Mac OSX Bad file descriptor
    I very much like to know!!


  • administrator

    hey @mdotedot, are you going to open-source your Stencyl extension? I'd like to check the errors you're having, calling .leave() really shouldn't crash the application. Cheers!



  • If all goes well the Stencyl Extension will become available to the Stencyl Community.

    It would be awesome if you could help me with the crash-code.

    Maybe you know of an alternative way to do the match(Error.Custom(Error.Blocked) line.


  • administrator

    @mdotedot would you mind sharing your extension on GitHub? if you don't wanna make it public for now, you can invite me privately first maybe. Cheers



  • Steps to reproduce:

    Followed : https://www.wikihow.com/Install-Node.Js-on-Windows to install NPM

    set PATH=c:\"Program Files"\nodejs;%HAXE_PATH%;%NEKOPATH%;%PATH%
    node -v
    
    npm install typescript
    

    Download colyseus examples: https://github.com/colyseus/colyseus-examples : Clone/Download ZIP
    Extract in C:\

    cd \colyseus-examples-master
    npm install
    
    npm run bundle-colyseus-client
    npm start
    

    Now for the HaXe Part:

    Download https://github.com/HaxeFoundation/haxe/releases/download/3.4.4/haxe-3.4.4-win64.exe

    c:> haxe-3.4.4-win65.exe
    

    let it create default c:\HaxeToolkit :

    Setting DOS Environment:

    set HAXE_PATH=c:\HaxeToolkit\haxe
    set NEKOPATH=c:\HaxeToolkit\neko
    set PATH=%HAXE_PATH%;%NEKOPATH%;%PATH%
    
    haxelib setup c:\HaxeToolkit\haxe\lib
    haxelib --global update haxelib
    
    haxelib install openfl
    haxelib run openfl setup
    haxelib run lime setup 
    lime create HelloWorld
    cd HelloWorld
    lime test html5
    
    lime setup windows
    

    Download Visual Studio 16
    vs_community__956414624.1551197993.exe
    Choose : Desktop development with C++ AND Click C++/CLI support

    lime test windows
    

    The installed 4.0.8 hxcpp does not contain run script apparantly , so set to original hxcpp:

    haxelib set hxcpp hxcpp 
    

    When 'Error : Could not process asset libraries' then do:

    openfl rebuild tools
    

    COLYSEUS
    Download ZIP from GitHub : https://github.com/colyseus/colyseus-hx
    Extract the archive to c:\HaxeToolkit so that the project.xml is in c:\HaxeToolkit\colyseus-hx-master

    Modify the Main.hx: to mkae sure that the localhost line is active and the "ws://colyseus-examples.herokuapp.com"); is commented out

              this.client = new Client("ws://localhost:2567");
    

    Build the NyanCat example

    c:> cd \HaxeToolkit\colyseus-hx-master\example\NyanCat
    C:\HaxeToolkit\colyseus-hx-master\example\NyanCat>lime build project.xml html5
    

    Error: Could not find haxelib "haxe-ws", does it need to be installed?

    Install haxe-ws 1.0.5

    haxelib install haxe-ws 
    
    lime test html5
    

    It should now produce the nyancat window where you can move around with the cursor keys

    C:\HaxeToolkit\colyseus-hx-master\example\NyanCat>lime build project.xml windows
    
    lime test windows 
    

    LEAVING ROOM Crash:

    Now we change the Main.hx to include leaving room

    this.client = new Client("ws://localhost:2567");
    	//this.client = new Client("ws://colyseus-examples.herokuapp.com");
    this.room = this.client.join("state_handler");
    var timer = new haxe.Timer(5000); 
    timer.run = function() {
    	trace(" Leave Room ");
    	this.room.leave();
    	var timer2=new haxe.Timer(2000);
    	timer2.run = function (){
       	   timer2.stop();
    	   trace(" Create new room ... " );
    	   this.room = this.client.join("state_handler");
        };
    };
    

    Build and lime test the windows version

    When you see CLIENT: ERROR you should stop the program and start the program again.

    Repeat to use lime test windows

    After a while; approximate < 10 minutes; it will crash



  • After two weeks of further debugging I've now came up with a work around so that it does not crash.

    These are the modifications that I needed to make to avoid null pointer crashes:

    SocketSys.hx (haxe/net/impl/SocketSys.hx)

    					// M.E. the match error.custom(error.blocked) caused null pointer crash
    					/*
                        needClose = !(e == 'Blocking' || (Std.is(e, Error) && (
                            (e:Error).match(Error.Custom(Error.Blocked)) ||
                            (e:Error).match(Error.Blocked))
                        ));
    					*/
    					if((""+e).indexOf("Block") < 0 && (""+e).indexOf("Custom(EOF)") < 0){
    						trace('closing socket because of $e');
    						needClose=true;
    					}
    				}
    

    And further down I don't close the socket which caused blocking and crashing things on Android

    	if (needClose) {
    		//	    close();  // M.E. This will cause Android problems
    	}
    

    Room.hx : avoid null pointer exception

    private function patch( binaryPatch: Bytes ) {
            // apply patch
    		// M.E. Check for null on binaryPatch
    		if(binaryPatch == null || this == null || this._previousState == null){
    trace("BINARY PATCH IS NULL or this._previousState == null !");
    		}else{
    			 
    			this._previousState = FossilDelta.Apply(this._previousState, binaryPatch);
    			// trigger state callbacks
    			this.set( MsgPack.decode( this._previousState ) );
    			this.onStateChange(this.state);
    		}
            
            
        }
    

    FossilDelta.hx in Apply to avoid null pointer exception

    	var total=0;
    	// M.E. Check for null on src
    	  if(src == null)return haxe.io.Bytes.alloc(0);
    	  if(delta == null) return haxe.io.Bytes.alloc(0);
    	  // M.E. End of modification
    	  
    

    And I needed to inform Stencyl about errors in Room.onError or the extension would still use room functions ...

    Room.hx connect function

    	this.connection.onError = function (e) {
    			var message="Possible causes: room's onAuth() failed or maxClients has been reached.";
    			if(getGameAttribute("ColyseusErrorMessage") == null)setGameAttribute("ColyseusErrorMessage", ""); setGameAttribute("ColyseusErrorMessage", ""+getGameAttribute("ColyseusErrorMessage")+" "+message); 
                trace(message);
                this.onError(e);
            };
    

    In the application I created an onError routine and that would be hit sometimes but then it will do a new client instance and it starts creating rooms again.

    So we don't close the socket on haxe.net level but detect problems in Room and Client on Colyseus level.

    Hopefully these changes don't bite me later. I really want to continue working on other methods than Lobby mechanism!

    Mac OSX, iOS Simulator, iPhone 5, Windows and Android all work without crashes when creating and leaving rooms now.
    I ran a demo-game that left and created rooms every 10 seconds and they kept working for an hour.
    Android crashed on one device after an hour. It had done over 100 room creations by that time.
    I need to keep that in mind when doing more stuff later on and see if it reoccurs.
    (HTML5 never gave problems as it didn't use haxe.net)



  • This week I've made progress on a simple room where players can be joined and walk around.
    I made it so that players would be moved simultaneous to see if the server could handle it.

    It didn't quite run well with 2 or more browser sessions at the same time. Also a windows publication didn't fare well.

    Yesterday I found out that the CPU on Windows and memory on HTML5 where draining. So today I made a simple version on my NyanCat project. I had it running on both html5 and windows and it had the same effect.

    Then I stripped colyseus from the nyancat so that only the image was left with controlling keys and there was no problem with CPU.

    With Colyseus:
    alt text

    And the non-colyseus version:

    alt text

    So Haxe client is consuming a lot of CPU !

    At least it wasn't my client implementation since both have the same problem. I guess I need multiple computers from now on to do tests ....



  • Last days I've worked on refactoring code and CPU investigations.

    Connection.hx modifications to avoid CPU consumption

     #if sys
    		while(!this.isClosed){ //M.E. After 200 reconnections the CPU was gone to 92%
    			this.ws.process();
    				
    			 Sys.sleep(0.005); // M.E. Avoid CPU consumption by introducing a really small sleep
            }
     #end
     public function close () {
    		this.isClosed=true; //M.E. add for thread checking
            this.ws.close();
     }
    		
    

    Perhaps the this._enqueuedSend mechanism can be altered as well to check for the isClosed state?!?! But I'm not sure what the enqueued mechanism needs this...

    Running with over more than 200 client initialization tests (and client.closing them) the CPU consumption kept relatively steady.
    Now the Windows executable doesn't take much of the CPU.

    Remains Javascript that consumes a lot of memory. But this couldn't easily be reproduced on the HaxeKit version, so it might be something related to other parts of HaXe and Stencyl.

    I was able to switch rooms on Android, Windows and HTML5 though. I need to test the other targets with the latest build to make sure I didn't break anything for them.

    Current state:

    • Initialize Colyseus (tested 200x re-initializations)
    • Create Room (Lobby, State_Handler) : Also done together with the 200x initializations
    • Get room list (getAvailableRooms)
    • Join Room
    • Leave Room
    • Player list
    • Send Data
    • Receive Data

    TODO:

    • Investigate Javascript memory consumption
    • Publication to all Stencyl targets
    • Ping / latency mechanism
    • Broadcast investigations
    • onAuth investigations
    • Different Room Types : RawData,TurnBased,LockData,SimplePhysics,Chat


    • javascript memory consumption was a Stencyl bug. It has to do with drawing stuff, which I obviously did in my test application. Filed a PR on Stencyl Forum for this.
    • Publications:
      iPhone Simulator
      iPhone (Should be fine on iPad as well)
      Mac OSX
      Linux
      Windows
      Android (Tablet+Phone)
      HTML5

    So all is fine to continue working on the RoomTypes.



  • Hello! do you use haxe only for client code or you have created externs for server as well?



  • Hello Serjek,

    Currently I'm working on Stencyl Extension which uses (modified) HaXe Colyseus Client code.
    The server is still running on TypeScript. A HaXe server would be awesome to have but I currently have my hands full with Client Code !

    Kind regards from
    M.E.