<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Proposal: automatic state synchronization in the client-side]]></title><description><![CDATA[<p>I'm in the process of making the state synchronization easier to apply in the client-side, since the usage of <a href="https://github.com/endel/delta-listener/" rel="nofollow"><code>delta-listener</code></a> often causes confusion. (<a href="https://github.com/gamestdio/colyseus.js/issues/12" rel="nofollow">colyseus.js#12</a>, <a href="https://github.com/gamestdio/colyseus-unity3d/issues/28" rel="nofollow">colyseus-unity3d#28</a>, ...)</p>
<p>The idea is to use <a href="https://tc39.github.io/proposal-decorators/" rel="nofollow">decorators</a> to annotate which properties should be synched. The <code>delta-listener</code> will be used under the hood by the auto-sync tool.</p>
<p>To initialize the synchronization, you would provide the room instance, and the root instance holding the annotations. The <code>initializeSync</code> method would register all listeners automatically, based on the annotations.</p>
<pre><code>let client = new Client(&quot;ws://localhost:2657&quot;);
let pongRoom = client.join(&quot;pong&quot;);

let game = new PongGame();
initializeSync(pongRoom, game); // registers all listeners automatically
</code></pre>
<p>Alright, so <code>game</code> is our root instance (of <code>PongGame</code>) holding the annotations. The names of the properties annotated in the client-side should be <strong>exactly the same</strong> as it is in the room state, in the server-side.</p>
<pre><code>let addToStage = (app, player) =&gt; app.stage.addChild(player);
let removeFromStage = (app, player) =&gt; app.stage.removeChild(player);

export class PongGame extends PIXI.Application {
    @syncMap(Player, addToStage, removeFromStage)
    players: EntityMap&lt;Player&gt; = {};

    @syncObject(Ball, addToStage, removeFromStage)
    ball: Ball;
}
</code></pre>
<p>The annotations used here are <code>@syncMap</code> and <code>@syncObject</code>. Both of them accept a callback for when the object is created and removed. Unfortunately, it's not possible to use an instance method as an argument in the decorator, because <code>this</code> is the class scope at that point.</p>
<p>Alright, so we mapped the <code>Player</code> and <code>Ball</code> as child of <code>PongGame</code>. They need to sync its properties as well. We'll use <code>@sync</code> for direct data mappings.</p>
<pre><code>export class Player extends PIXI.Graphics {
    @sync() x: number;
    @sync() y: number;
}
</code></pre>
<p>Given that <code>Player</code> is mapped through <code>PongGame</code>'s <code>players</code>  property, the sync tool will register listeners for <code>&quot;players/:id/x&quot;</code> and <code>&quot;players/:id/y&quot;</code>.</p>
<p>What's interesting is that you can provide name aliases, and even use setters when synching data directly:</p>
<pre><code>export class Ball extends PIXI.Graphics {
    /* mapping &quot;x&quot; property to &quot;nextX&quot; */
    @sync('x') nextX: number;

    /* mapping &quot;y&quot; property to &quot;nextY&quot; setter */
    @sync('y') 
    set nextY (value) {
        this._nextY = value;
    }
}
</code></pre>
<p>That's it for now.  I still need to figure out how to deal with deeper objects, like <code>&quot;players/:id/items/:id/x&quot;</code> - which still doesn't work at the moment.</p>
<p>The API is still work in progress, feedback is very welcome! :rocket:<br />
Cheers!</p>
]]></description><link>http://discuss.colyseus.io/topic/6/proposal-automatic-state-synchronization-in-the-client-side</link><generator>RSS for Node</generator><lastBuildDate>Fri, 15 May 2026 05:41:26 GMT</lastBuildDate><atom:link href="http://discuss.colyseus.io/topic/6.rss" rel="self" type="application/rss+xml"/><pubDate>Sat, 21 Oct 2017 03:03:01 GMT</pubDate><ttl>60</ttl><item><title><![CDATA[Reply to Proposal: automatic state synchronization in the client-side on Sat, 21 Oct 2017 03:11:39 GMT]]></title><description><![CDATA[<p>I'm in the process of making the state synchronization easier to apply in the client-side, since the usage of <a href="https://github.com/endel/delta-listener/" rel="nofollow"><code>delta-listener</code></a> often causes confusion. (<a href="https://github.com/gamestdio/colyseus.js/issues/12" rel="nofollow">colyseus.js#12</a>, <a href="https://github.com/gamestdio/colyseus-unity3d/issues/28" rel="nofollow">colyseus-unity3d#28</a>, ...)</p>
<p>The idea is to use <a href="https://tc39.github.io/proposal-decorators/" rel="nofollow">decorators</a> to annotate which properties should be synched. The <code>delta-listener</code> will be used under the hood by the auto-sync tool.</p>
<p>To initialize the synchronization, you would provide the room instance, and the root instance holding the annotations. The <code>initializeSync</code> method would register all listeners automatically, based on the annotations.</p>
<pre><code>let client = new Client(&quot;ws://localhost:2657&quot;);
let pongRoom = client.join(&quot;pong&quot;);

let game = new PongGame();
initializeSync(pongRoom, game); // registers all listeners automatically
</code></pre>
<p>Alright, so <code>game</code> is our root instance (of <code>PongGame</code>) holding the annotations. The names of the properties annotated in the client-side should be <strong>exactly the same</strong> as it is in the room state, in the server-side.</p>
<pre><code>let addToStage = (app, player) =&gt; app.stage.addChild(player);
let removeFromStage = (app, player) =&gt; app.stage.removeChild(player);

export class PongGame extends PIXI.Application {
    @syncMap(Player, addToStage, removeFromStage)
    players: EntityMap&lt;Player&gt; = {};

    @syncObject(Ball, addToStage, removeFromStage)
    ball: Ball;
}
</code></pre>
<p>The annotations used here are <code>@syncMap</code> and <code>@syncObject</code>. Both of them accept a callback for when the object is created and removed. Unfortunately, it's not possible to use an instance method as an argument in the decorator, because <code>this</code> is the class scope at that point.</p>
<p>Alright, so we mapped the <code>Player</code> and <code>Ball</code> as child of <code>PongGame</code>. They need to sync its properties as well. We'll use <code>@sync</code> for direct data mappings.</p>
<pre><code>export class Player extends PIXI.Graphics {
    @sync() x: number;
    @sync() y: number;
}
</code></pre>
<p>Given that <code>Player</code> is mapped through <code>PongGame</code>'s <code>players</code>  property, the sync tool will register listeners for <code>&quot;players/:id/x&quot;</code> and <code>&quot;players/:id/y&quot;</code>.</p>
<p>What's interesting is that you can provide name aliases, and even use setters when synching data directly:</p>
<pre><code>export class Ball extends PIXI.Graphics {
    /* mapping &quot;x&quot; property to &quot;nextX&quot; */
    @sync('x') nextX: number;

    /* mapping &quot;y&quot; property to &quot;nextY&quot; setter */
    @sync('y') 
    set nextY (value) {
        this._nextY = value;
    }
}
</code></pre>
<p>That's it for now.  I still need to figure out how to deal with deeper objects, like <code>&quot;players/:id/items/:id/x&quot;</code> - which still doesn't work at the moment.</p>
<p>The API is still work in progress, feedback is very welcome! :rocket:<br />
Cheers!</p>
]]></description><link>http://discuss.colyseus.io/post/6</link><guid isPermaLink="true">http://discuss.colyseus.io/post/6</guid><dc:creator><![CDATA[endel]]></dc:creator><pubDate>Sat, 21 Oct 2017 03:11:39 GMT</pubDate></item><item><title><![CDATA[Reply to Proposal: automatic state synchronization in the client-side on Invalid Date]]></title><description><![CDATA[<p><strong>Update</strong>: almost ready to use!</p>
<p>I've also added a <code>@listen</code> method annotation. It's a shorthand for <code>room.listen()</code>:</p>
<pre><code>class Something {
    @listen(&quot;players/:id/score&quot;)
    doSomethingWithScore (change: DataChange) {
        console.log(change.path.id, change.value);
    }
}
</code></pre>
<p>And a very simple Pong game is playable here: <a href="https://colyseus-pong.herokuapp.com/" rel="nofollow">https://colyseus-pong.herokuapp.com/</a><br />
The sources are available here: <a href="https://github.com/endel/colyseus-auto-sync/" rel="nofollow">https://github.com/endel/colyseus-auto-sync/</a></p>
<p>Next step is combining synching with client-prediction. This Heroku instance is in the U.S. and the gameplay feels very laggy for me.</p>
<p>Cheers! :rocket:</p>
]]></description><link>http://discuss.colyseus.io/post/7</link><guid isPermaLink="true">http://discuss.colyseus.io/post/7</guid><dc:creator><![CDATA[endel]]></dc:creator><pubDate>Invalid Date</pubDate></item><item><title><![CDATA[Reply to Proposal: automatic state synchronization in the client-side on Invalid Date]]></title><description><![CDATA[<p><a class="plugin-mentions-user plugin-mentions-a" href="http://discuss.colyseus.io/uid/1">@endel</a>  Thoughts on how you would implement this in Unity?</p>
<p>Also do you have a good example of a first state sync of a complex state structure in Unity?</p>
]]></description><link>http://discuss.colyseus.io/post/18</link><guid isPermaLink="true">http://discuss.colyseus.io/post/18</guid><dc:creator><![CDATA[codrobin33]]></dc:creator><pubDate>Invalid Date</pubDate></item><item><title><![CDATA[Reply to Proposal: automatic state synchronization in the client-side on Invalid Date]]></title><description><![CDATA[<p>Hi <a class="plugin-mentions-user plugin-mentions-a" href="http://discuss.colyseus.io/uid/8">@codrobin33</a>, I haven't researched much how to do it using C#. I think it's possible to achieve a pretty similar API.</p>
<p>The initial state has been fixed recently on the JavaScript client - the callback of <code>room.listen()</code> is being triggered by deep additions. This is currently missing in the C# client. (<a href="https://github.com/gamestdio/colyseus-unity3d/issues/31" rel="nofollow">colyseus-unity3d#31</a>)</p>
]]></description><link>http://discuss.colyseus.io/post/19</link><guid isPermaLink="true">http://discuss.colyseus.io/post/19</guid><dc:creator><![CDATA[endel]]></dc:creator><pubDate>Invalid Date</pubDate></item><item><title><![CDATA[Reply to Proposal: automatic state synchronization in the client-side on Invalid Date]]></title><description><![CDATA[<p><a class="plugin-mentions-user plugin-mentions-a" href="http://discuss.colyseus.io/uid/8">@codrobin33</a> listening to the initial state has been fixed for Unity today (<a href="https://github.com/gamestdio/colyseus-unity3d/releases/tag/0.8.2" rel="nofollow">v0.8.2</a>)</p>
]]></description><link>http://discuss.colyseus.io/post/21</link><guid isPermaLink="true">http://discuss.colyseus.io/post/21</guid><dc:creator><![CDATA[endel]]></dc:creator><pubDate>Invalid Date</pubDate></item><item><title><![CDATA[Reply to Proposal: automatic state synchronization in the client-side on Invalid Date]]></title><description><![CDATA[<p><a class="plugin-mentions-user plugin-mentions-a" href="http://discuss.colyseus.io/uid/1">@endel</a> what a quick turnaround! thank you!</p>
<p>I will try out my project and see if this fixes what i was struggling with.</p>
]]></description><link>http://discuss.colyseus.io/post/22</link><guid isPermaLink="true">http://discuss.colyseus.io/post/22</guid><dc:creator><![CDATA[codrobin33]]></dc:creator><pubDate>Invalid Date</pubDate></item><item><title><![CDATA[Reply to Proposal: automatic state synchronization in the client-side on Invalid Date]]></title><description><![CDATA[<p><a class="plugin-mentions-user plugin-mentions-a" href="http://discuss.colyseus.io/uid/1">@endel</a> hey sir im still struggling with initial sync, here is my setup.</p>
<p>Error i'm receiving:</p>
<pre><code class="language-InvalidCastException:">Colyseus.Room.ParseMessage (System.Byte[] recv) (at Assets/Plugins/Colyseus/Room.cs:140)
Colyseus.Room.Recv () (at Assets/Plugins/Colyseus/Room.cs:73)
Colyseus.Client.Recv () (at Assets/Plugins/Colyseus/Client.cs:90)
RoomHandler+&lt;Start&gt;c__Iterator0.MoveNext () (at Assets/RoomHandler.cs:37)
UnityEngine.SetupCoroutine.InvokeMoveNext (IEnumerator enumerator, IntPtr returnValueAddress) (at /Users/builduser/buildslave/unity/build/Runtime/Export/Coroutines.cs:17)
</code></pre>
<p>Here is my data from server:<br />
<img src="/assets/uploads/files/1513310225940-screen-shot-2017-12-14-at-9.53.52-pm-resized.png" alt="0_1513310230592_Screen Shot 2017-12-14 at 9.53.52 PM.png" class="img-responsive img-markdown" /></p>
<p>Code section its dying on (Room.cs) in plugins:</p>
<pre><code>} else if (code == Protocol.ROOM_STATE) {
				byte[] encodedState = (byte[]) message [2];

				// TODO:
				// https://github.com/deniszykov/msgpack-unity3d/issues/8

				// var remoteCurrentTime = (double) message [3];
				// var remoteElapsedTime = (int) message [4];

				// this.SetState (state, remoteCurrentTime, remoteElapsedTime);

				this.SetState (encodedState, 0, 0);

			}
</code></pre>
<p>And finally, my start function</p>
<pre><code>IEnumerator Start () 
    {
        client = new Colyseus.Client (&quot;ws://localhost:2657&quot;);
        client.OnOpen += OnOpenHandler;
        client.OnClose += (object sender, EventArgs e) =&gt; room.Leave();

        yield return StartCoroutine(client.Connect());

        room = client.Join(&quot;game&quot;);
        room.OnReadyToConnect += (sender, e) =&gt; StartCoroutine ( room.Connect() );
        room.Listen (&quot;messages/:number&quot;, this.OnMessageAdded);
        room.Listen (&quot;players/:id&quot;, this.OnPlayerAdded);
        room.Listen (&quot;shared/:map/:bases/:id&quot;, this.OnBaseAdded);
        room.OnJoin += OnRoomJoined;
        room.OnUpdate += OnUpdateHandler;

        room.OnData += (object sender, MessageEventArgs e) =&gt; Debug.Log(e.data);

        while (true)
        {
            client.Recv();

            // string reply = client.RecvString();
            if (client.error != null)
            {
                Debug.LogError (&quot;Error: &quot; + client.error);
                break;
            }

            yield return 0;
        }

        OnApplicationQuit();
    }
</code></pre>
<p>Have any initial thoughts?</p>
]]></description><link>http://discuss.colyseus.io/post/31</link><guid isPermaLink="true">http://discuss.colyseus.io/post/31</guid><dc:creator><![CDATA[codrobin33]]></dc:creator><pubDate>Invalid Date</pubDate></item><item><title><![CDATA[Reply to Proposal: automatic state synchronization in the client-side on Invalid Date]]></title><description><![CDATA[<p>Hey <a class="plugin-mentions-user plugin-mentions-a" href="http://discuss.colyseus.io/uid/8">@codrobin33</a>! Are you using the latest version of the server and client? Would be great if you could provide me an example project to reproduce the error. (btw it's recommended to report bugs on the issue tracker <a href="https://github.com/gamestdio/colyseus-unity3d/issues" rel="nofollow">https://github.com/gamestdio/colyseus-unity3d/issues</a>)</p>
]]></description><link>http://discuss.colyseus.io/post/32</link><guid isPermaLink="true">http://discuss.colyseus.io/post/32</guid><dc:creator><![CDATA[endel]]></dc:creator><pubDate>Invalid Date</pubDate></item><item><title><![CDATA[Reply to Proposal: automatic state synchronization in the client-side on Invalid Date]]></title><description><![CDATA[<p><a class="plugin-mentions-user plugin-mentions-a" href="http://discuss.colyseus.io/uid/1">@endel</a> I was looking at this in a tired state, but im pretty sure i had fully updated client and server. Ill double check what i was seeing and if i still have an error I'll open an issue on the github. Thanks for the response.</p>
]]></description><link>http://discuss.colyseus.io/post/34</link><guid isPermaLink="true">http://discuss.colyseus.io/post/34</guid><dc:creator><![CDATA[codrobin33]]></dc:creator><pubDate>Invalid Date</pubDate></item></channel></rss>