From 8d824459af5c9b7157da6364dc2ef6292b036c3c Mon Sep 17 00:00:00 2001 From: Justin Bakse Date: Wed, 12 Jan 2022 14:49:33 -0600 Subject: [PATCH] clarify tanks example --- public/examples/tanks/README.md | 34 +++++++++++++++++++++++++++++++++ public/examples/tanks/index.js | 27 +++++++++++--------------- public/examples/tanks/notes.md | 26 ------------------------- 3 files changed, 45 insertions(+), 42 deletions(-) delete mode 100644 public/examples/tanks/notes.md diff --git a/public/examples/tanks/README.md b/public/examples/tanks/README.md index 46060d1..6c9a99c 100644 --- a/public/examples/tanks/README.md +++ b/public/examples/tanks/README.md @@ -4,3 +4,37 @@ - **space** to fire > Open this example in two browser windows at once! + +# A Common Problem + +When two players try to write to the same shared object at the same time a conflict occurs. Some data is lost and its possible for things to get into a bad state. A conflict occurs even if the changes are limited to different properties of the shared object. + +One common time a conflict like this happens is when the host is trying to animate some data in a shared object (like moving a bullet every frame) and another player tires to change _anything_ in the same shared object. + +# More Generally + +This kind of problem comes up when 1) multiple clients write to the same record and 2) the writing is frequent (typically every frame). + +If only one client is writing to the shared there is no potential for conficts. + +If writing is infrequent (typically event based rather than every frame), the potential for a conflict is there, but its less likely to happen and much less likely to happen repeatedly leading to a bad state. + +# A Partial Solution + +On way to partially work around this by having players create new bullets in their own shared object. The host then moves them over into the main shared. + +This way the players don't write to shared at all, only the host does. + +This partially works but... + +The player writes new bullets to its own participant share + the host removes bullets from that share so they are both writing to the same share. If nothing else is going on, this is fine. The host writes to the participant shared _in response_ to the participant writing to it, so they won't write at the same time. + +But, if the participant is writing something else to the shared object, and writing it frequently (like the player is turning while shooting, so lots of updates to the tank angle) we end up with the same kind of problem. + +# A Little Better + +This example goes a step further and uses second shared object to communicate new bullets to the host. All particpants write new bullets there, the host will remove them and add them to the main shared. Now we have multiple players and the host writing to the same record, but not rapidly. So we can still get a conflict, but its much less likely. + +This is starting to look a lot like a message sending or event scheme. A real messaging system could prevent conflicts in this case. + +See tanks_emit for an example that uses messages to solve this problem. diff --git a/public/examples/tanks/index.js b/public/examples/tanks/index.js index 5f091f8..9ec11e2 100644 --- a/public/examples/tanks/index.js +++ b/public/examples/tanks/index.js @@ -1,5 +1,4 @@ /* global uuidv4 */ -/* global partyEmit partySubscribe*/ class Rect { constructor(l = 0, t = 0, w = 0, h = 0) { @@ -23,12 +22,12 @@ function pointInRect(p, r) { const bounds = new Rect(0, 0, 400, 400); -let shared, send, me, participants; +let shared, new_bullets, me, participants; function preload() { partyConnect("wss://deepstream-server-1.herokuapp.com", "tanks", "main"); shared = partyLoadShared("shared"); - send = partyLoadShared("send"); + new_bullets = partyLoadShared("send"); me = partyLoadMyShared(); participants = partyLoadParticipantShareds(); } @@ -40,28 +39,23 @@ function setup() { if (partyIsHost()) { shared.bullets = []; - send.bullets = []; + new_bullets.bullets = []; } - me.tank = { x: 100, y: 100, a: 0 }; - partySubscribe("createBullet", (d) => { - if (partyIsHost()) { - console.log("partySubscribe", d); - } - }); + me.tank = { x: 100, y: 100, a: 0 }; } function draw() { checkKeys(); - showData(); if (partyIsHost()) stepGame(); drawScene(); + showData(); } function stepGame() { - // copy sent bullets to main bullet array - while (send.bullets.length) { - shared.bullets.push(send.bullets.shift()); + // move sent bullets to main bullet array + while (new_bullets.bullets.length) { + shared.bullets.push(new_bullets.bullets.shift()); } // step bullets @@ -75,6 +69,7 @@ function showData() { "\t" ); } + function drawScene() { background("#cc6666"); shared.bullets.forEach(drawBullet); @@ -110,17 +105,17 @@ function drawBullet(b) { function keyPressed() { if (key === " ") { - send.bullets.push({ + new_bullets.bullets.push({ x: me.tank.x, y: me.tank.y, dX: sin(me.tank.a) * 6, dY: -cos(me.tank.a) * 6, }); - partyEmit("createBullet", "hell yeah"); } return false; } + function checkKeys() { // forward if (keyIsDown(87) /*w*/) { diff --git a/public/examples/tanks/notes.md b/public/examples/tanks/notes.md deleted file mode 100644 index 541962d..0000000 --- a/public/examples/tanks/notes.md +++ /dev/null @@ -1,26 +0,0 @@ -when two players try to alter the same share object at the same time, one or the other share object "wins" - -even if the changes are to different properties of the shared object, one change is lost - -this commonly happens when the host is trying to animate some data in a shared object (like moving a bullet) and another player tires to change _anything_ in the same shared object - -i just tried working around this by having players add bullets in their own shared object and the host moves them over into the main shared. - -this way the players don't write to shared at all - -it partly worked but... - -now the player writes new bullets to its own participant share + the host removes bullets from that share so they are both writing to the same share. If the player is writing to it a lot (like the player is turning when the shot is fired) we end up with the same kind of problem. - -possible solution: -create a second global shared -all particpants write new bullets there -host consumes them -now we have multiple players + hosts writing to the same record, but not rapidly. - -This is starting to look a lot like a message sending or event scheme. And deepstream does have Event stuff built in, and maybe that should be exposed by p5.party? - -https://deepstream.io/tutorials/core/pubsub/ - -con - more complixty to the API -pro - maybe less complexity/workarounds in sketch code