{"id":933,"date":"2013-08-13T21:57:51","date_gmt":"2013-08-13T20:57:51","guid":{"rendered":"http:\/\/www.aymericlamboley.fr\/blog\/?p=933"},"modified":"2014-11-01T14:35:26","modified_gmt":"2014-11-01T13:35:26","slug":"amf-p2p-serialization-tricks","status":"publish","type":"post","link":"http:\/\/www.aymericlamboley.fr\/blog\/amf-p2p-serialization-tricks\/","title":{"rendered":"AMF &amp; P2P Serialization Tricks"},"content":{"rendered":"<p><a href=\"http:\/\/www.bilub.com\/news\/\"><img decoding=\"async\" loading=\"lazy\" src=\"http:\/\/www.aymericlamboley.fr\/blog\/wp-content\/uploads\/2013\/08\/team_colors.png\" alt=\"Play Bilu Ball with friends\" width=\"790\" height=\"172\" class=\"alignnone size-full wp-image-1017\" srcset=\"http:\/\/www.aymericlamboley.fr\/blog\/wp-content\/uploads\/2013\/08\/team_colors.png 790w, http:\/\/www.aymericlamboley.fr\/blog\/wp-content\/uploads\/2013\/08\/team_colors-300x65.png 300w\" sizes=\"(max-width: 790px) 100vw, 790px\" \/><\/a><\/p>\n<p>My name is Caius and I have been working on a multiplayer real time football\/air hockey football game <img decoding=\"async\" alt=\"Play Bilub\" src=\"http:\/\/www.bilub.com\/soccer_ball.ico\" \/> <a title=\"Play Bilub\" href=\"http:\/\/www.bilub.com\/\" target=\"_blank\">http:\/\/www.bilub.com\/<\/a> and I had to make the communication between players as light as possible.<\/p>\n<p><!--more--><\/p>\n<p>I created a small example to show you how you can optimize the way flash sends objects over the wire.<\/p>\n<p>I started off with this object:<\/p>\n<pre>package com.game.actions\r\n{\r\nimport com.game.GameConstants;\r\n\r\nimport flash.utils.ByteArray;\r\nimport flash.utils.IDataInput;\r\nimport flash.utils.IDataOutput;\r\nimport flash.utils.IExternalizable;\r\n\r\npublic class PlayerInputAction\r\n{\r\npublic var input:int;\r\npublic var senderId:uint;\r\npublic var scheduledFrame:uint;\r\npublic var hostFrame:uint;\r\n\r\npublic function PlayerInputAction():void\r\n{\r\n}\r\n}\r\n}<\/pre>\n<p>The following code returns:<\/p>\n<pre>registerClassAlias(\"com.game.actions.PlayerInputAction\", com.game.actions.PlayerInputAction);<\/pre>\n<pre>var player:PlayerInputAction = new PlayerInputAction();\r\nvar bytesElemByte:ByteArray = new ByteArray();\r\nbytesElemByte.writeObject( player );\r\ntrace(\"PlayerInputAction FULL \", bytesElemByte.length) -- outputs: PlayerInputAction FULL 95 !!! WOW very big<\/pre>\n<p>1. First thing I noticed is that you need to keep the class alias name short. So i changed<\/p>\n<pre>registerClassAlias(\"3\", com.game.actions.PlayerInputAction);<\/pre>\n<p>the same code outputs: PlayerInputAction FULL <strong> 62<\/strong>, an improvement but we can do better \ud83d\ude42<\/p>\n<p>&nbsp;<\/p>\n<p>2. Then i found this article <a href=\"http:\/\/jacksondunstan.com\/articles\/2248\/comment-page-1#comment-152405\" target=\"_blank\">http:\/\/jacksondunstan.com\/articles\/2248\/comment-page-1#comment-152405<\/a> that discusses this problem as well. I implement the IExternalizable interface and the writeExternal &amp; readExternal methods.<\/p>\n<pre>package com.game.actions\r\n{\r\nimport com.game.GameConstants;\r\n\r\nimport flash.utils.ByteArray;\r\nimport flash.utils.IDataInput;\r\nimport flash.utils.IDataOutput;\r\nimport flash.utils.IExternalizable;\r\n\r\npublic class PlayerInputAction implements IExternalizable\r\n{\r\npublic var input:int;\/\/4 bytes\r\npublic var senderId:uint; \/\/4 bytes\r\npublic var scheduledFrame:uint; \/\/4 bytes\r\npublic var hostFrame:uint; \/\/4 bytes\r\n\r\npublic var executed:Boolean = false;\r\n\r\npublic function PlayerInputAction():void\r\n{\r\n}\r\n\r\npublic function writeExternal( output:IDataOutput ): void\r\n{\r\noutput.writeByte( input ) \/\/ 1 byte\r\noutput.writeUnsignedInt( senderId ) \/\/ 4 bytes\r\noutput.writeUnsignedInt( scheduledFrame ) \/\/ 4 bytes\r\noutput.writeUnsignedInt( hostFrame ) \/\/ 4 bytes\r\n\r\n}\r\n\r\npublic function readExternal( dataInput:IDataInput ): void\r\n{\r\ninput = dataInput.readByte()\r\nsenderId = dataInput.readUnsignedInt()\r\nscheduledFrame = dataInput.readUnsignedInt()\r\nhostFrame = dataInput.readUnsignedInt()\r\n}\r\n\r\npublic function pack():ByteArray{\r\nvar output:ByteArray = new ByteArray();\r\noutput.writeByte( GameConstants.PLAYER_INPUT_ACTION );\r\noutput.writeByte( input )\r\noutput.writeUnsignedInt( senderId )\r\noutput.writeUnsignedInt( scheduledFrame )\r\noutput.writeUnsignedInt( hostFrame )\r\nreturn output;\r\n}\r\n\r\npublic function unpack( dataInput:ByteArray ):void{\r\ninput = dataInput.readByte()\r\nsenderId = dataInput.readUnsignedInt()\r\nscheduledFrame = dataInput.readUnsignedInt()\r\nhostFrame = dataInput.readUnsignedInt()\r\n}\r\n\r\npublic function toString():String{\r\nreturn \"[PlayerInputAction] p-\"+senderId+\" scheduledFrame \"+int(scheduledFrame);\r\n}\r\n}\r\n}<\/pre>\n<p>&nbsp;<\/p>\n<p>This greatly decreases the size of the bytearray.<\/p>\n<pre>var player:PlayerInputAction = new PlayerInputAction();\r\nvar bytesElemByte:ByteArray = new ByteArray();\r\nbytesElemByte.writeObject( player );\r\ntrace(\"PlayerInputAction FULL \", bytesElemByte.length) \/\/outputs PlayerInputAction FULL  <strong>17<\/strong><\/pre>\n<p>because of<\/p>\n<pre>output.writeByte( input ) \/\/ 1 byte\r\noutput.writeUnsignedInt( senderId ) \/\/ 4 bytes\r\noutput.writeUnsignedInt( scheduledFrame ) \/\/ 4 bytes\r\noutput.writeUnsignedInt( hostFrame ) \/\/ 4 bytes<\/pre>\n<p>+ 4 extra bytes that probably are the prototype of the object<\/p>\n<p>3. Writting my own method pack() to serialize the object offered me the lowest size.<\/p>\n<pre>public function pack():ByteArray{\r\nvar output:ByteArray = new ByteArray();\r\noutput.writeByte( GameConstants.PLAYER_INPUT_ACTION ); \/\/ i had to add 1 byte that helps me know the type of that object.\r\noutput.writeByte( input )\r\noutput.writeUnsignedInt( senderId )\r\noutput.writeUnsignedInt( scheduledFrame )\r\noutput.writeUnsignedInt( hostFrame )\r\nreturn output;\r\n}\r\nvar player:PlayerInputAction = new PlayerInputAction();\r\nvar temp:ByteArray = player.pack();\r\ntrace(\"PlayerInputAction LIGHT \", temp.length) \/\/ PlayerInputAction LIGHT  14<\/pre>\n<p>So if you really want to have a light communication between clients, you can make a small effort and write your own pack\/unpack methods to transform the object into a bytearray. It may take more time  to implement but it is worth it \ud83d\ude42<\/p>\n<p>4. The last solution is a mix between the solution above that takes advantage of byte array compression. If the length of the byte array is small you do not need compress, but in my case for 2 players I have an average of 100 bytes and compressing returns 41 bytes.<\/p>\n<pre>\r\npublic function pack():ByteArray{\r\n\t\t\tvar gameByte:ByteArray = new ByteArray();\r\n\t\t\tgameByte.writeObject( this );\r\n\/\/\t\t\tLoggerPro.instance.log(this, LoggerPro.DEBUG, \"pack1\", gameByte.length  )\r\n\t\t\tgameByte.compress( CompressionAlgorithm.DEFLATE );\r\n\t\t\tgameByte.position = 0;\r\n\t\t\tvar resp:ByteArray = new ByteArray(); \r\n\t\t\tresp.writeByte( GameConstants.GAME_STATE_ACTION );\r\n\t\t\tresp.writeBytes( gameByte );\r\n\/\/\t\t\tLoggerPro.instance.log(this, LoggerPro.DEBUG, \"pack2\", resp.length )\r\n\t\t\treturn resp;\r\n\t\t}\r\n\t\t\r\n\t\tpublic function unpack( resp:ByteArray ):GameStateAction{\r\n\t\t\tvar payload:ByteArray = new ByteArray(); \r\n\t\t\tresp.readBytes( payload )\r\n\t\t\tpayload.position = 0;\r\n\t\t\tpayload.uncompress( CompressionAlgorithm.DEFLATE );\r\n\t\t\treturn payload.readObject();\r\n\t\t}\r\n<\/pre>\n<p>Also you can continue optimizing the size by using writeByte instead of writeUnsignedInt or writeInt, but in my case I have values &gt; 255( 1 byte ) so I had to send 4 bytes per each variable.<\/p>\n<p>&nbsp;<\/p>\n<p>You can see the game and the communication in action here <img decoding=\"async\" alt=\"Play Bilub\" src=\"http:\/\/www.bilub.com\/soccer_ball.ico\" \/> <a title=\"Play Bilub\" href=\"http:\/\/www.bilub.com\/\" target=\"_blank\">http:\/\/www.bilub.com\/<\/a> and I am looking forward for your feeback.<\/p>\n<p>&nbsp;<\/p>\n<p>May the best BILU win \ud83d\ude42<\/p>\n","protected":false},"excerpt":{"rendered":"<p>My name is Caius and I have been working on a multiplayer real time football\/air hockey football game http:\/\/www.bilub.com\/ and I had to make the communication between players as light as possible.<\/p>\n","protected":false},"author":3,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_monsterinsights_skip_tracking":false,"_monsterinsights_sitenote_active":false,"_monsterinsights_sitenote_note":"","_monsterinsights_sitenote_category":0},"categories":[4,11,90],"tags":[],"_links":{"self":[{"href":"http:\/\/www.aymericlamboley.fr\/blog\/wp-json\/wp\/v2\/posts\/933"}],"collection":[{"href":"http:\/\/www.aymericlamboley.fr\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"http:\/\/www.aymericlamboley.fr\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"http:\/\/www.aymericlamboley.fr\/blog\/wp-json\/wp\/v2\/users\/3"}],"replies":[{"embeddable":true,"href":"http:\/\/www.aymericlamboley.fr\/blog\/wp-json\/wp\/v2\/comments?post=933"}],"version-history":[{"count":16,"href":"http:\/\/www.aymericlamboley.fr\/blog\/wp-json\/wp\/v2\/posts\/933\/revisions"}],"predecessor-version":[{"id":1252,"href":"http:\/\/www.aymericlamboley.fr\/blog\/wp-json\/wp\/v2\/posts\/933\/revisions\/1252"}],"wp:attachment":[{"href":"http:\/\/www.aymericlamboley.fr\/blog\/wp-json\/wp\/v2\/media?parent=933"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"http:\/\/www.aymericlamboley.fr\/blog\/wp-json\/wp\/v2\/categories?post=933"},{"taxonomy":"post_tag","embeddable":true,"href":"http:\/\/www.aymericlamboley.fr\/blog\/wp-json\/wp\/v2\/tags?post=933"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}