-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathtutorial-guide_tuningdebugging.html
144 lines (122 loc) · 19 KB
/
tutorial-guide_tuningdebugging.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Fine Tuning and Debugging - Documentation</title>
<link rel="shortcut icon" href="http://lance.gg/favicon.ico">
<link rel="icon" sizes="16x16 32x32 64x64" href="http://lance.gg/favicon.ico">
<link rel="icon" type="image/png" sizes="196x196" href="http://lance.gg/favicon-192.png">
<link rel="icon" type="image/png" sizes="160x160" href="http://lance.gg/favicon-160.png">
<link rel="icon" type="image/png" sizes="96x96" href="http://lance.gg/favicon-96.png">
<link rel="icon" type="image/png" sizes="64x64" href="http://lance.gg/favicon-64.png">
<link rel="icon" type="image/png" sizes="32x32" href="http://lance.gg/favicon-32.png">
<link rel="icon" type="image/png" sizes="16x16" href="http://lance.gg/favicon-16.png">
<!--[if lt IE 9]>
<script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->
<link href="https://file.myfontastic.com/DeXq9523CzrFERZkXSzP7D/icons.css" rel="stylesheet">
<link type="text/css" rel="stylesheet" href="https://code.ionicframework.com/ionicons/2.0.1/css/ionicons.min.css">
<link type="text/css" rel="stylesheet" href="styles/jsdoc-default.css">
<link rel="stylesheet" href="//cdn.jsdelivr.net/highlight.js/9.8.0/styles/atelier-sulphurpool-light.min.css">
</head>
<body>
<input type="checkbox" id="nav-trigger" class="nav-trigger" />
<label for="nav-trigger" class="navicon-button x">
<div class="navicon"></div>
</label>
<label for="nav-trigger" class="overlay"></label>
<nav>
<h2 class="home"><a href="index.html">Lance</a></h2><h2>Concepts</h2><ul class="tutorials"><li><span class='category'>Introduction<ul><li><a href="tutorial-introduction.html">Introduction</a></li><li><a href="tutorial-introduction_prologue.html">Prologue</a></li><li><a href="tutorial-introduction_community.html">Community</a></li><li><a href="tutorial-introduction_roadmap.html">Development Roadmap</a></li><li><a href="tutorial-introduction_faq.html">Frequently Asked Questions</a></li></ul></li><li><span class='category'>Overview<ul><li><a href="tutorial-overview_architecture.html">Architecture of a Multiplayer Game</a></li><li><a href="tutorial-choosing_a_physics_engine.html">Choosing a Physics Engine</a></li><li><a href="tutorial-guide_gameengine.html">Game Engine</a></li><li><a href="tutorial-guide_serverengine.html">Server Engine</a></li><li><a href="tutorial-guide_clientengine.html">Client Engine</a></li><li><a href="tutorial-guide_renderer.html">Renderer</a></li><li><a href="tutorial-guide_gameworld.html">Game World and Game Objects</a></li><li><a href="tutorial-guide_serialization.html">Serialization and Communication</a></li></ul></li><li><span class='category'>Synchronization Methods<ul><li><a href="tutorial-guide_syncinterpolation.html">Interpolation</a></li><li><a href="tutorial-guide_syncextrapolation.html">Extrapolation</a></li></ul></li><li><span class='category'>Tutorials<ul><li><a href="tutorial-MyFirstGame.html">My First Game: Pong</a></li><li><a href="tutorial-spaceships.html">Spaaace</a></li></ul></li><li><span class='category'>Extras<ul><li><a href="tutorial-guide_tuningdebugging.html">Fine Tuning and Debugging</a></li><li><a href="tutorial-furtherreading.html">Further Reading</a></li></ul></li></ul><h2>API Reference</h2><h3 class="classes">Classes</h3><ul><li><a href="AFrameRenderer.html">AFrameRenderer</a><ul class='methods'><li data-type='method'><a href="AFrameRenderer.html#draw">draw</a></li><li data-type='method'><a href="AFrameRenderer.html#init">init</a></li></ul></li><li><a href="BaseTypes.html">BaseTypes</a></li><li><a href="CannonPhysicsEngine.html">CannonPhysicsEngine</a></li><li><a href="ClientEngine.html">ClientEngine</a><ul class='methods'><li data-type='method'><a href="ClientEngine.html#connect">connect</a></li><li data-type='method'><a href="ClientEngine.html#disconnect">disconnect</a></li><li data-type='method'><a href="ClientEngine.html#sendInput">sendInput</a></li><li data-type='method'><a href="ClientEngine.html#start">start</a></li></ul></li><li><a href="DynamicObject.html">DynamicObject</a><ul class='methods'><li data-type='method'><a href="DynamicObject.html#bendingToString">bendingToString</a></li><li data-type='method'><a href="DynamicObject.html#toString">toString</a></li></ul></li><li><a href="GameEngine.html">GameEngine</a><ul class='methods'><li data-type='method'><a href="GameEngine.html#addObjectToWorld">addObjectToWorld</a></li><li data-type='method'><a href="GameEngine.html#getPlayerGameOverResult">getPlayerGameOverResult</a></li><li data-type='method'><a href="GameEngine.html#isOwnedByPlayer">isOwnedByPlayer</a></li><li data-type='method'><a href="GameEngine.html#on">on</a></li><li data-type='method'><a href="GameEngine.html#once">once</a></li><li data-type='method'><a href="GameEngine.html#processInput">processInput</a></li><li data-type='method'><a href="GameEngine.html#registerClasses">registerClasses</a></li><li data-type='method'><a href="GameEngine.html#removeListener">removeListener</a></li><li data-type='method'><a href="GameEngine.html#removeObjectFromWorld">removeObjectFromWorld</a></li><li data-type='method'><a href="GameEngine.html#start">start</a></li><li data-type='method'><a href="GameEngine.html#step">step</a></li></ul></li><li><a href="GameObject.html">GameObject</a><ul class='methods'><li data-type='method'><a href="GameObject.html#bendingToString">bendingToString</a></li><li data-type='method'><a href="GameObject.html#hasComponent">hasComponent</a></li><li data-type='method'><a href="GameObject.html#onAddToWorld">onAddToWorld</a></li><li data-type='method'><a href="GameObject.html#onRemoveFromWorld">onRemoveFromWorld</a></li><li data-type='method'><a href="GameObject.html#syncTo">syncTo</a></li><li data-type='method'><a href="GameObject.html#toString">toString</a></li></ul></li><li><a href="GameWorld.html">GameWorld</a><ul class='methods'><li data-type='method'><a href="GameWorld.html#addObject">addObject</a></li><li data-type='method'><a href="GameWorld.html#forEachObject">forEachObject</a></li><li data-type='method'><a href="GameWorld.html#getNewId">getNewId</a></li><li data-type='method'><a href="GameWorld.html#queryObject">queryObject</a></li><li data-type='method'><a href="GameWorld.html#queryObjects">queryObjects</a></li><li data-type='method'><a href="GameWorld.html#removeObject">removeObject</a></li></ul></li><li><a href="KeyboardControls.html">KeyboardControls</a></li><li><a href="PhysicalObject2D.html">PhysicalObject2D</a><ul class='methods'><li data-type='method'><a href="PhysicalObject2D.html#onAddToWorld">onAddToWorld</a></li><li data-type='method'><a href="PhysicalObject2D.html#toString">toString</a></li></ul></li><li><a href="PhysicalObject3D.html">PhysicalObject3D</a><ul class='methods'><li data-type='method'><a href="PhysicalObject3D.html#toString">toString</a></li></ul></li><li><a href="Quaternion.html">Quaternion</a><ul class='methods'><li data-type='method'><a href="Quaternion.html#conjugate">conjugate</a></li><li data-type='method'><a href="Quaternion.html#copy">copy</a></li><li data-type='method'><a href="Quaternion.html#multiply">multiply</a></li><li data-type='method'><a href="Quaternion.html#set">set</a></li><li data-type='method'><a href="Quaternion.html#setFromAxisAngle">setFromAxisAngle</a></li><li data-type='method'><a href="Quaternion.html#slerp">slerp</a></li><li data-type='method'><a href="Quaternion.html#toAxisAngle">toAxisAngle</a></li><li data-type='method'><a href="Quaternion.html#toString">toString</a></li></ul></li><li><a href="Renderer.html">Renderer</a><ul class='methods'><li data-type='method'><a href="Renderer.html#addObject">addObject</a></li><li data-type='method'><a href="Renderer.html#draw">draw</a></li><li data-type='method'><a href="Renderer.html#init">init</a></li><li data-type='method'><a href="Renderer.html#removeObject">removeObject</a></li><li data-type='method'><a href="Renderer.html#runClientStep">runClientStep</a></li><li data-type='method'><a href="Renderer.html#stop">stop</a></li></ul></li><li><a href="Serializer.html">Serializer</a><ul class='methods'><li data-type='method'><a href="Serializer.html#.typeCanAssign">typeCanAssign</a></li><li data-type='method'><a href="Serializer.html#addCustomType">addCustomType</a></li><li data-type='method'><a href="Serializer.html#registerClass">registerClass</a></li></ul></li><li><a href="ServerEngine.html">ServerEngine</a><ul class='methods'><li data-type='method'><a href="ServerEngine.html#gameStatus">gameStatus</a></li></ul></li><li><a href="SimplePhysicsEngine.html">SimplePhysicsEngine</a></li><li><a href="ThreeVector.html">ThreeVector</a><ul class='methods'><li data-type='method'><a href="ThreeVector.html#add">add</a></li><li data-type='method'><a href="ThreeVector.html#clone">clone</a></li><li data-type='method'><a href="ThreeVector.html#copy">copy</a></li><li data-type='method'><a href="ThreeVector.html#getBendingDelta">getBendingDelta</a></li><li data-type='method'><a href="ThreeVector.html#length">length</a></li><li data-type='method'><a href="ThreeVector.html#lerp">lerp</a></li><li data-type='method'><a href="ThreeVector.html#multiplyScalar">multiplyScalar</a></li><li data-type='method'><a href="ThreeVector.html#normalize">normalize</a></li><li data-type='method'><a href="ThreeVector.html#set">set</a></li><li data-type='method'><a href="ThreeVector.html#subtract">subtract</a></li><li data-type='method'><a href="ThreeVector.html#toString">toString</a></li></ul></li><li><a href="Trace.html">Trace</a></li><li><a href="TwoVector.html">TwoVector</a><ul class='methods'><li data-type='method'><a href="TwoVector.html#add">add</a></li><li data-type='method'><a href="TwoVector.html#clone">clone</a></li><li data-type='method'><a href="TwoVector.html#copy">copy</a></li><li data-type='method'><a href="TwoVector.html#getBendingDelta">getBendingDelta</a></li><li data-type='method'><a href="TwoVector.html#length">length</a></li><li data-type='method'><a href="TwoVector.html#lerp">lerp</a></li><li data-type='method'><a href="TwoVector.html#multiplyScalar">multiplyScalar</a></li><li data-type='method'><a href="TwoVector.html#normalize">normalize</a></li><li data-type='method'><a href="TwoVector.html#set">set</a></li><li data-type='method'><a href="TwoVector.html#subtract">subtract</a></li><li data-type='method'><a href="TwoVector.html#toString">toString</a></li></ul></li></ul><h3 class="events">Events</h3><ul><li><a href="GameEngine.html#event:client__postStep">client__postStep</a></li><li><a href="GameEngine.html#event:client__preStep">client__preStep</a></li><li><a href="GameEngine.html#event:client__processInput">client__processInput</a></li><li><a href="GameEngine.html#event:client__slowFrameRate">client__slowFrameRate</a></li><li><a href="GameEngine.html#event:client__stepReset">client__stepReset</a></li><li><a href="GameEngine.html#event:client__syncReceived">client__syncReceived</a></li><li><a href="GameEngine.html#event:objectAdded">objectAdded</a></li><li><a href="GameEngine.html#event:objectDestroyed">objectDestroyed</a></li><li><a href="GameEngine.html#event:playerDisconnected">playerDisconnected</a></li><li><a href="GameEngine.html#event:playerJoined">playerJoined</a></li><li><a href="GameEngine.html#event:postStep">postStep</a></li><li><a href="GameEngine.html#event:preStep">preStep</a></li><li><a href="GameEngine.html#event:processInput">processInput</a></li><li><a href="GameEngine.html#event:server__inputReceived">server__inputReceived</a></li><li><a href="GameEngine.html#event:server__playerDisconnected">server__playerDisconnected</a></li><li><a href="GameEngine.html#event:server__playerJoined">server__playerJoined</a></li><li><a href="GameEngine.html#event:server__postStep">server__postStep</a></li><li><a href="GameEngine.html#event:server__preStep">server__preStep</a></li><li><a href="GameEngine.html#event:server__processInput">server__processInput</a></li><li><a href="GameEngine.html#event:start">start</a></li><li><a href="GameEngine.html#event:syncReceived">syncReceived</a></li></ul>
</nav>
<div id="main">
<h1 class="page-title">Fine Tuning and Debugging</h1>
<section>
<header>
</header>
<article>
<p>In the prologue of this documentation, the complexity of multiplayer network games becomes evident. But that
is small potatoes compared to debugging multiplayer network games. The debugging of a multiplayer game
is made difficult by the following reasons:</p>
<ol>
<li>A multiplayer network game consists of several non-symmetric hosts running non-symmetric software, separated by non-symmetric networks.</li>
<li>The problem may be very hard to reproduce, and exists only under certain race conditions.</li>
<li>The problem may be in the game mechanics as implemented in the game engine. For example, in the game of Pong, one might forget to check if the left paddle was hit <em>before</em> one checks if the left wall was hit (true story).</li>
<li>Then again, the problem may be in the configuration of the synchronization. For example, in the game of Pong, one might forget to disable velocity interpolation, and when the ball's velocity changes direction, the velocity interpolation causes strange visual effects (also, a true story).</li>
<li>Then again, the problem may be due to network spikes.</li>
<li>And finally, there is always the possibility that the game in question is not a good match for the architecture presented in this
guide, and some of the assumptions in the game may need to be revisited in order to make the multiplayer experience viable with the proposed architecture.</li>
</ol>
<p>In all the cases above, the game developer sees the same symptom: an object jumps around strangely on the screen!</p>
<p>At first this can be <em>very</em> frustrating, because the game developer does not know where to start. This document
was written to help with the process of debugging multiplayer networked games using an organized process which
help to narrow down the problem efficiently.</p>
<h2>The basic debugging process</h2><p>The following steps define an efficient debugging process:</p>
<ol>
<li>Is the problem also on the Server, or just when the client tries to render the visuals?<ol>
<li>If the problem is on the server, use server traces to see how the objects states change at each step.</li>
<li>If the problem is only on the client, is the problem related to Synchronization?</li>
</ol>
</li>
<li>Is the problem caused by unusual network conditions?</li>
</ol>
<h2>Server or Client</h2><p>The problem is always first observed on the client. To determine if the problem
exists on the server as well, the easiest approach is to use the Reflect synchronization
strategy. The Reflect method is not really a synchronization strategy but rather a degenerate
synchronization method which simply shows the server sync data as it arrives.</p>
<p>To run a game in reflect mode, simply use the URL query string option:</p>
<pre class="prettyprint source"><code>http://127.0.0.1:3000/?sync=reflect</code></pre><p>The problem can also occur in such a way that it usually happens in between syncs.
In this case, one can configure the server to increase the update rate so that
it approaches the client's render rate.</p>
<p>The ServerEngine has options to set the step rate and the sync update rate:</p>
<pre class="prettyprint source lang-javascript"><code>const serverEngine = new MyServerEngine(io, gameEngine, {
updateRate: 6,
stepRate: 60,
});</code></pre><p>Lastly, an important technique is to test the game on a local network (i.e. run server
and clients on same host) to make sure that network delay is not a factor.</p>
<h2>Check Server Traces</h2><p>To enable server traces, set the trace level when the game engine is created:</p>
<pre class="prettyprint source lang-javascript"><code>const gameEngine = new MyGameEngine({ traceLevel: 1 });</code></pre><p>See the Trace class for trace constants. The trace output file is called <code>server.trace</code>
and it will show trace data which looks as follows:</p>
<pre class="prettyprint source"><code>[2016-12-04T22:16:15.136Z]719>game engine processing input[10052] <up> from playerId 1
[2016-12-04T22:16:15.153Z]720>game engine processing input[10053] <up> from playerId 1
[2016-12-04T22:16:15.153Z]720>========== sending world update 720 ==========
[2016-12-04T22:16:15.170Z]721>game engine processing input[10054] <up> from playerId 1
[2016-12-04T22:16:15.170Z]721>========== destroying object DynamicObject[2] position(273.854, 304.23, NaN) velocity(-4.529, -2.119, NaN) angle205 ==========</code></pre><p>Each line starts with a timestamp, and a step number. The example above shows
steps 719-720. At the lowest trace level, the service will show each object's
attributes, including position, at the end of every step.</p>
<p>To make debugging easier, you may want to implement your object's <code>toString()</code> method,
to show a useful representation of the object. This will be <em>very useful</em> in the long run.</p>
<h2>Check Client Traces</h2><p>Enabling client traces can be done from the URL's query string, using the <em>traceLevel</em>
parameter. Here is an example:</p>
<pre class="prettyprint source"><code>http://127.0.0.1:3000/?sync=reflect&traceLevel=0</code></pre><p>Since there are multiple clients, there are also multiple trace files, one for each client. The trace files are named <code>client.<n>.trace</code>.</p>
<h3>Client Interpolation</h3><p>Note that in interpolation, the client actually plays a step which is older than the current
step. Ensure you check the trace to see which step the interpolation is targeting.
Interpolation runs the client's game engine in passive mode, meaning that the game engine
on the client will not execute the physics steps.</p>
<h3>Client Extrapolation</h3><p>For extrapolation synchronization, the client will re-enact the required history of steps
each time a new sync is received. This is traced in low-level detail. To debug
problems related to bending, look at the position attributes of the relevant object
just before the sync was received, and the result of the re-enactment before bending
was applied, and the final object position after bending was applied.</p>
</article>
</section>
</div>
<br class="clear">
<footer>
Did you find a mistake? Do you have an improvement? <a href="https://github.com/lance-gg/lance/issues">Let us know!</a>
</footer>
<script>
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','https://www.google-analytics.com/analytics.js','ga');
ga('create', 'UA-88335360-1', 'auto');
ga('send', 'pageview');
</script>
<script src="https://use.typekit.net/lai1bbe.js"></script>
<script>try{Typekit.load({ async: true });}catch(e){}</script>
<script src="//cdnjs.cloudflare.com/ajax/libs/highlight.js/9.8.0/highlight.min.js"></script>
<script>hljs.initHighlightingOnLoad();</script>
<script src="scripts/linenumber.js"></script>
</body>
</html>