diff --git a/README.md b/README.md index 8f297ea..162b20e 100644 --- a/README.md +++ b/README.md @@ -17,10 +17,10 @@ Test Coverage: -![Statements](https://img.shields.io/badge/statements-99.62%25-brightgreen.svg?style=flat) -![Branches](https://img.shields.io/badge/branches-94.21%25-brightgreen.svg?style=flat) -![Functions](https://img.shields.io/badge/functions-97.91%25-brightgreen.svg?style=flat) -![Lines](https://img.shields.io/badge/lines-99.59%25-brightgreen.svg?style=flat) +![Statements](https://img.shields.io/badge/statements-98.26%25-brightgreen.svg?style=flat) +![Branches](https://img.shields.io/badge/branches-92.03%25-brightgreen.svg?style=flat) +![Functions](https://img.shields.io/badge/functions-96.55%25-brightgreen.svg?style=flat) +![Lines](https://img.shields.io/badge/lines-98.12%25-brightgreen.svg?style=flat) diff --git a/notes/how_to.md b/notes/how_to.md index 4ae29c4..2a0347b 100644 --- a/notes/how_to.md +++ b/notes/how_to.md @@ -1,8 +1,22 @@ +# How to run local dev + +1. `npm run start` to build/watch/rebuild +2. use VS Code / Live Server to open examples in browser + +# How to run tests + +1. `npm run serve` to start local version of the server (leave running) +2. `npm run test` to run the test suite + -or- +3. `npm run serve` to start local version of the server (leave running) +4. run tests from VS Code interface with `Jest` extension + # How To Release a new Version of p5.Party Releases are automated using `np`. 1. Make sure the code is working. +1. Make sure local ds server is running so tests can run. 1. Make sure your working directory is clean. 1. Make sure your commits are pushed. 1. Make sure you are up to date with origin. @@ -11,7 +25,7 @@ Releases are automated using `np`. 1. Enter one-time password from Duo Mobile 1. Browser should open github release page 1. Enter name for release (or leave blank to use release number) -1. Attach p5.party.zip (generated by build in your working directory) +1. ~~Attach p5.party.zip (generated by build in your working directory)~~ 1. Check pre-release checkbox 1. Click "publish" diff --git a/notes/newTodo.todo b/notes/newTodo.todo index 6c2b602..ea15225 100644 --- a/notes/newTodo.todo +++ b/notes/newTodo.todo @@ -1,6 +1,12 @@ +□ something to consider: + ✓ does resolved promise resolve again immediately if waited on again? (a: yes) @done(22-06-27 11:14) + ✓ if so, we could cache the load() promise on the object and use that for a better when ready/loaded. @done(22-06-27 11:14) + ✓ though that might mean some wrapping code in the method @done(22-06-27 11:14) + ✓ move the load call into the constructor? @done(22-08-04 12:33) + Docs: - □ make note about empty {} not being in guests[] - □ if you want everyone to be in there as soon as they connect, add a placehold prop `partyGetMyShared({placeholder: true})` + ✓ make note about empty {} not being in guests[] @done(22-06-27 11:38) + ✓ if you want everyone to be in there as soon as they connect, add a placehold prop `partyGetMyShared({placeholder: true})` @done(22-06-27 11:38) Restore Features: ✓ test p5.party loading callbacks @done(22-06-01 21:04) @@ -10,7 +16,7 @@ Docs: ✓ partyLoadGuestShareds(); @done(22-06-03 11:18) ✓ partyWatchShared() @done(22-06-06 14:50) ✓ partyEmit() partySubscribe() @done(22-06-06 14:59) - □ ? partyToggleInfo() - panel + ✓ pong example doesn't work because guests[] doesn't contain the same object as me. (it has a different copy of the record/shared object) @done(22-06-07 21:55) ✓ D12 is breaking for same reason! @done(22-06-07 21:55) @@ -18,47 +24,64 @@ Docs: ✓ ghosts does not work because guests[].length can't be used to determine join order any more. (can be fixed by putting a property on getMyShared()) @done(22-06-07 21:55) Code Review: - □ check what is public/private - □ order class members (is there an extension for this) - □ hacky #updateGuestShareds - instead of current hacky #updateGuestShareds, maybe try keeping - two arrays: `#guestShareds` and `#nonemptyGuestShareds` + ✓ check what is public/private @done(22-08-04 12:44) + Intermission: □ check test coverage - □ compare SLOC - □ compare export size (try more modern es version target?) - □ extension is "better comments" better than "highlight" + □ extensions: is "better comments" better than "highlight"? Deploy: - □ create a branch for 0.7 - □ add student credit to readme - □ bump tsc version, remove ts suffix in code - □ squash tsc to main - □ push to github, verify netlify - □ release + ✓ add student credit to readme @done(22-06-08 12:42) + ✓ bump tsc version, remove ts suffix in code @done(22-06-08 12:42) + ✓ squash tsc to main @done(22-06-08 12:42) + ✓ push to github, verify netlify @done(22-06-08 12:42) + ✓ release @done(22-06-08 12:42) Examples: - □ move multifile examples to modules - □ remove bad examples (tanks 1, drag 1) + ✓ move multifile examples to modules @done(22-08-04 14:46) + ✓ remove bad examples (tanks 1, drag 1) @done(22-08-04 14:46) ✓ remove ! examples @done(22-06-07 13:05) ✓ fix live server injecting into html displayed on example pages? @done(22-06-07 21:59) - + □ e3_sign_up example puts input field at bottom of page + □ callbacks example puts button at bottom of page Clean up: ✓ remove .js files @done(22-06-06 23:37) ✓ remove old dev deps @done(22-06-06 23:37) ✓ update npm scripts now that js dev deps are gone @done(22-06-07 11:14) - + □ clean up branches (merge + remove feature branches) -Add Features: + +Priority Features: □ overwrite on reopening room - partyLoadShared takes an initobject which is only used if the record doesn't exist yet. Might be better to set default to overwriting record contents if connector is host? + partyLoadShared takes an initobject which is only used if the record doesn't exist yet. Might be better to set default to overwriting record contents if connector is host? Or some way to signal you want this? a `resetEmptyRoom` flag or call. + Currently i find i'm more likely to use setup()->partyIsHost->partySetShared thanthe initobject. the init object just isn't more convient (which was the point!) + + +Possible Features: + □ allow early calls to Record initData, setData, watchShared, delete + these currently expect the dsRecord to be ready and fail if not + they could wait for whenLoaded() instead with a little refactoring + wait until need is demonstrated + + □ show warning or error on writes to "owned" records + it is generally a good practice to only read from `guests` array + and write to only `my` + though sometimes, with careful planning, it is alright to violate this general practice + □ consider allowing but warning when this practice is violated + □ consider erroring when this practice is violated (locked `guests`) + □ consider allowing user to mark owner of shared objects and enforce with warnings or errors - □ "owned" record warnings/errors - □ actually, it might be best to just make guests[] locked □ record tracking, garbage collect, room reset - □ room.deleteRecord - □ tsc javadocs? + currently all server records for named shared objects are leaked + p5.party does not maintan list of records created + user records and guest records are not deleted automatically (unless the server restarts) + there is no way to manually delete a record + □ create room.deleteRecord + + □ tsc javadocs + □ restore partyToggleInfo() - panel + the earlier version of p5.party had an info panel that showed who was connected and who was host, maybe restore it? \ No newline at end of file diff --git a/notes/todo.todo b/notes/todo.todo index 9301c42..a543af7 100644 --- a/notes/todo.todo +++ b/notes/todo.todo @@ -34,12 +34,9 @@ Shared objects: Records: - ☐ records leak - currently all server records are leaked. - there are no lists of records created - user records and guest records are not deleted (unless the server restarts) - ☐ clear room/app records + + ☐ api to clear room/app records Would be useful in normal, emergency, and dev situations. Having stale data stick around can be confusing reloading a js program usually restarts it from 0, but not when you have data hanging out on the back end. pattern: `setup->if host->reset room->init room` @@ -103,12 +100,10 @@ Emit Subscribe: Release Package: - ☐ provide min and unmin versions? - compare to other p5 libraries - https://stackoverflow.com/questions/25956937/how-to-build-minified-and-uncompressed-bundle-with-webpack - ☐ Should the examples be distributed in the zip? - ☐ Should the zip even be distributed? + ☐ provide min and unmin versions, include them in the release resources + + Examples: ☐ Div Cursors Example diff --git a/package.json b/package.json index 797b50a..8618483 100644 --- a/package.json +++ b/package.json @@ -38,10 +38,8 @@ "prebuild": "npm run genversion", "build": "npm run esbuild -- --outfile=dist/p5.party.js", "prerelease": "npm run build", - "prepublishOnly": "npm run build && npm run zip", + "prepublishOnly": "npm run build", "postpublish": "curl https://purge.jsdelivr.net/npm/p5.party@latest/dist/p5.party.js", - "prezip": "rm -f p5.party.zip", - "zip": "npm run build && ln -s public/examples && zip p5.party.zip -r dist -r examples && rm -r examples && unzip -l p5.party.zip", "genversion": "genversion --es6 --semi version.js", "esbuild": "esbuild src/p5.party.ts --bundle --sourcemap --minify --target=es6 --define:global=window --inject:esbuild.inject.js" }, diff --git a/public/examples/d12/index.js b/public/examples/d12/index.js index 71cd03d..29be96d 100644 --- a/public/examples/d12/index.js +++ b/public/examples/d12/index.js @@ -333,6 +333,7 @@ function keyPressed() { if (inputMode === "move") { if (key === " " || keyCode === RETURN) { startMessageOnRelease = true; + return false; } if (key === "q") { me.avatarId = ++me.avatarId % 16; diff --git a/public/examples/drag_1/.eslintrc.js b/public/examples/drag/.eslintrc.js similarity index 100% rename from public/examples/drag_1/.eslintrc.js rename to public/examples/drag/.eslintrc.js diff --git a/public/examples/drag_2/README.md b/public/examples/drag/README.md similarity index 97% rename from public/examples/drag_2/README.md rename to public/examples/drag/README.md index d607bdd..9204ca9 100644 --- a/public/examples/drag_2/README.md +++ b/public/examples/drag/README.md @@ -1,4 +1,4 @@ -# drag2 +# drag Some little squres to drag around. diff --git a/public/examples/drag_1/index.html b/public/examples/drag/index.html similarity index 100% rename from public/examples/drag_1/index.html rename to public/examples/drag/index.html diff --git a/public/examples/drag_2/index.js b/public/examples/drag/index.js similarity index 97% rename from public/examples/drag_2/index.js rename to public/examples/drag/index.js index ec3b9f0..5950966 100644 --- a/public/examples/drag_2/index.js +++ b/public/examples/drag/index.js @@ -29,6 +29,7 @@ window.draw = () => { window.mousePressed = () => { // abort if any sprites are already in drag + // this requires players to take turns with dragging if (shared.sprites.find((s) => s.inDrag)) return; for (const s of shared.sprites.slice().reverse()) { diff --git a/public/examples/drag_1/shape.js b/public/examples/drag/shape.js similarity index 100% rename from public/examples/drag_1/shape.js rename to public/examples/drag/shape.js diff --git a/public/examples/drag_1/README.md b/public/examples/drag_1/README.md deleted file mode 100644 index 8fe9708..0000000 --- a/public/examples/drag_1/README.md +++ /dev/null @@ -1,11 +0,0 @@ -# drag1 - -Some little squres to drag around. - -- **drag** to move a square - -> Try this example in two browser windows at once! - -This example has a conflict bug! The state of all three squares is kept in one shared object. While a participant drags a square, it sends frequent updates. If two guests are dragging squares at once, conflicts will occur. - -The Drag2 example only allows one square to be dragged at once. This limits the functionality, but prevents the conflict. diff --git a/public/examples/drag_1/index.js b/public/examples/drag_1/index.js deleted file mode 100644 index 2d0d8b6..0000000 --- a/public/examples/drag_1/index.js +++ /dev/null @@ -1,90 +0,0 @@ -import { Point, Rect, pointInRect } from "./shape.js"; - -const my_id = Math.random(); - -let shared; - -window.preload = () => { - partyConnect("wss://deepstream-server-1.herokuapp.com", "drag_1", "main"); - shared = partyLoadShared("shared"); -}; - -window.setup = () => { - createCanvas(400, 400); - noStroke(); - - if (partyIsHost()) { - shared.sprites = []; - shared.sprites.push(initSprite(new Rect(10, 10, 100, 100), "#ffff66")); - shared.sprites.push(initSprite(new Rect(30, 30, 100, 100), "#ff66ff")); - shared.sprites.push(initSprite(new Rect(50, 50, 100, 100), "#66ffff")); - } -}; - -window.draw = () => { - background("#cc6666"); - shared.sprites.forEach(stepSprite); - shared.sprites.forEach(drawSprite); -}; - -window.mousePressed = () => { - for (const s of shared.sprites.slice().reverse()) { - if (mousePressedSprite(s)) break; - } -}; - -window.mouseReleased = () => { - for (const s of shared.sprites.slice().reverse()) { - if (mouseReleasedSprite(s)) break; - } -}; - -function initSprite(rect = new Rect(), color = "red") { - const s = {}; - s.rect = rect; - s.color = color; - return s; -} - -function stepSprite(s) { - if (s.inDrag && s.owner === my_id) { - s.rect.l = mouseX + s.dragOffset.x; - s.rect.t = mouseY + s.dragOffset.y; - } -} - -function drawSprite(s) { - push(); - fill(s.color); - noStroke(); - if (s.inDrag) { - strokeWeight(3); - stroke("black"); - } - rect(s.rect.l, s.rect.t, s.rect.w, s.rect.h); - pop(); -} - -function mousePressedSprite(s) { - if (!s.inDrag && pointInRect(new Point(mouseX, mouseY), s.rect)) { - // begin drag - s.inDrag = true; - s.owner = my_id; - s.dragOffset = new Point(s.rect.l - mouseX, s.rect.t - mouseY); - - // move to top - const i = shared.sprites.indexOf(s); - shared.sprites.splice(i, 1); - shared.sprites.push(s); - return true; - } - return false; -} - -function mouseReleasedSprite(s) { - if (s.owner === my_id) { - s.inDrag = false; - s.owner = null; - } - return false; -} diff --git a/public/examples/drag_2/.eslintrc.js b/public/examples/drag_2/.eslintrc.js deleted file mode 100644 index 34b87e3..0000000 --- a/public/examples/drag_2/.eslintrc.js +++ /dev/null @@ -1,6 +0,0 @@ -/* global module */ -module.exports = { - parserOptions: { - sourceType: "module", - }, -}; diff --git a/public/examples/drag_2/index.html b/public/examples/drag_2/index.html deleted file mode 100644 index 0fd5905..0000000 --- a/public/examples/drag_2/index.html +++ /dev/null @@ -1,23 +0,0 @@ - - - - -
- -
- -

index.js

-
- -

index.html

-
- - - - - - - - - - diff --git a/public/examples/e4_sound_board/index.html b/public/examples/e4_sound_board/index.html index 120932d..9ba50a3 100644 --- a/public/examples/e4_sound_board/index.html +++ b/public/examples/e4_sound_board/index.html @@ -13,6 +13,7 @@

index.html

+ diff --git a/public/examples/e4_sound_board/index.js b/public/examples/e4_sound_board/index.js index d57ddb4..17bd08d 100644 --- a/public/examples/e4_sound_board/index.js +++ b/public/examples/e4_sound_board/index.js @@ -59,7 +59,7 @@ function mousePressed() { // ? function onPlaySound(name) { - console.log("onPlaySound", name); + console.log("onPlaySound", name, sounds); // ? sounds[name].play(); diff --git a/public/examples/hello_cdn/README.md b/public/examples/hello_cdn/README.md index 4893b02..dec8c93 100644 --- a/public/examples/hello_cdn/README.md +++ b/public/examples/hello_cdn/README.md @@ -1,10 +1,16 @@ # hello_cdn -This example shows how to connect to a party server, load a shared data object, and read and write to it. It also shows how to share a p5 color. +This example shows how to connect to a party server, load a shared data object, and read and write to it. -This example is the same as `hello_party` but using a cdn to load p5.party. The JavaScript is exactly the same, the only difference is in the html file. +- **click** to move the dot + +This example is the same as `hello_party` but loads p5.party from a CDN instead of a local file. + +CDN stands for Content Delivery Network. A CDN hosts commonly accessed content like js libraries. -Loading p5.party from a CDN is handy for quick sketching. +Loading p5.party from a CDN introduces an external dependency; if the CDN removes the file, your app won't be able to load it. + +Using a CDN is good for quick sketching, and also lets you always load the latest version of the library rather than a spcific version which is sometimes useful. Loading the latest version from the CDN: @@ -15,11 +21,9 @@ Loading the latest version from the CDN: Loading a specific version from the CDN: ```html - + ``` Try [this example on the p5 web editor.](https://editor.p5js.org/jbakse/sketches/O3hvfPac2) -- **click** to move the dot - > Try this example in two browser windows at once! diff --git a/public/examples/hello_cdn/index.html b/public/examples/hello_cdn/index.html index dfe0ad1..a4773c4 100644 --- a/public/examples/hello_cdn/index.html +++ b/public/examples/hello_cdn/index.html @@ -2,13 +2,28 @@ +
+ +
+ +

index.js

+
+ +

index.html

+
+ + - + + + + + diff --git a/public/examples/hello_cdn/index.js b/public/examples/hello_cdn/index.js index 917e678..b1a98ca 100644 --- a/public/examples/hello_cdn/index.js +++ b/public/examples/hello_cdn/index.js @@ -6,18 +6,12 @@ function preload() { // begin loading shared object // setup() won't be called until the shared object is loaded - shared = partyLoadShared("shared"); + shared = partyLoadShared("shared", { x: 0, y: 0 }); } function setup() { createCanvas(400, 400); noStroke(); - - // set defaults on shared data - shared.x ??= 0; - shared.y ??= 0; - - // see: http://mdn.io/nullish-assign } function mousePressed() { diff --git a/public/examples/stickies/index.html b/public/examples/stickies/index.html index 120932d..fc5384a 100644 --- a/public/examples/stickies/index.html +++ b/public/examples/stickies/index.html @@ -2,7 +2,11 @@ -
+
+
+ + +
diff --git a/public/examples/stickies/index.js b/public/examples/stickies/index.js index e0e9ca8..77d6b4d 100644 --- a/public/examples/stickies/index.js +++ b/public/examples/stickies/index.js @@ -1,5 +1,3 @@ -/* global uuidv4 */ - class Rect { constructor(l = 0, t = 0, w = 0, h = 0) { this.l = l; @@ -20,7 +18,7 @@ function pointInRect(p, r) { return p.x > r.l && p.x < r.l + r.w && p.y > r.t && p.y < r.t + r.h; } -const my_id = uuidv4(); +const my_id = Math.random(); // quick and dirty id let shared; diff --git a/public/examples/tanks_2/README.md b/public/examples/tanks/README.md similarity index 100% rename from public/examples/tanks_2/README.md rename to public/examples/tanks/README.md diff --git a/public/examples/tanks/debug.js b/public/examples/tanks/debug.js new file mode 100644 index 0000000..cb868c1 --- /dev/null +++ b/public/examples/tanks/debug.js @@ -0,0 +1,12 @@ +export function debugShow(data) { + const roundIt = (key, value) => { + if (typeof value === "number") return Math.floor(value * 100) / 100; + return value; + }; + + document.getElementById("info").innerText = JSON.stringify( + data, + roundIt, + " " + ); +} diff --git a/public/examples/tanks_1/index.html b/public/examples/tanks/index.html similarity index 79% rename from public/examples/tanks_1/index.html rename to public/examples/tanks/index.html index e461606..de24340 100644 --- a/public/examples/tanks_1/index.html +++ b/public/examples/tanks/index.html @@ -8,14 +8,18 @@ #create { padding: 10px; } - #shared { + #info { background: #eee; white-space: pre; + font-family: monospace; } -
+
+
+
+
@@ -27,7 +31,7 @@

index.html

- + diff --git a/public/examples/tanks_2/index.js b/public/examples/tanks/index.js similarity index 86% rename from public/examples/tanks_2/index.js rename to public/examples/tanks/index.js index 66685a8..3a9d1f4 100644 --- a/public/examples/tanks_2/index.js +++ b/public/examples/tanks/index.js @@ -1,34 +1,28 @@ -// p5.party experimental - -// project utils - -// @todo: switch to modules, would clean up global declartions below - -/* global Rect pointInRect */ -/* global StatTracker */ -/* global debugShow */ +import { StatTracker } from "./stats.js"; +import { debugShow } from "./debug.js"; +import { pointInRect, Rect } from "./shape.js"; let stats; let shared, my, guests; -function preload() { +window.preload = () => { partyConnect("wss://deepstream-server-1.herokuapp.com", "tanks_2", "main"); shared = partyLoadShared("shared", { bullets: [] }); my = partyLoadMyShared(); guests = partyLoadGuestShareds(); -} +}; -function setup() { +window.setup = () => { createCanvas(500, 400).parent("canvas-wrap"); - stats = new StatTracker(); + stats = new StatTracker(my); my.tank = { x: random(width), y: random(height), a: random(2 * PI), spin: 0 }; // hosting can change mid-game so every client subscribes, and then checks if it is host on every message partySubscribe("createBullet", onCreateBullet); -} +}; -function draw() { +window.draw = () => { moveTank(); if (partyIsHost()) stepGame(); drawScene(); @@ -38,7 +32,7 @@ function draw() { stats, guests: guests, }); -} +}; /////////////////////////////////////////// // HOST CODE @@ -91,7 +85,7 @@ function moveTank() { my.tank.a += my.tank.spin; } -function keyPressed() { +window.keyPressed = () => { if (key === " ") { partyEmit("createBullet", { x: my.tank.x + sin(my.tank.a) * 24, @@ -102,7 +96,7 @@ function keyPressed() { } return false; -} +}; /////////////////////////////////////////// // CLIENT CODE - DRAW @@ -131,3 +125,9 @@ function drawBullet(b) { ellipse(b.x, b.y, 10, 10); pop(); } + +window.addEventListener("keydown", function (e) { + if (e.keyCode == 32 && e) { + e.preventDefault(); + } +}); diff --git a/public/examples/drag_2/shape.js b/public/examples/tanks/shape.js similarity index 100% rename from public/examples/drag_2/shape.js rename to public/examples/tanks/shape.js diff --git a/public/examples/tanks_2/stats.js b/public/examples/tanks/stats.js similarity index 80% rename from public/examples/tanks_2/stats.js rename to public/examples/tanks/stats.js index 62b34b4..5a567aa 100644 --- a/public/examples/tanks_2/stats.js +++ b/public/examples/tanks/stats.js @@ -1,19 +1,16 @@ -/* global uuidv4 */ -/* global my */ - -/* exported StatTracker */ -class StatTracker { +export class StatTracker { #id; #frames; #startTime; - constructor() { + constructor(my) { this.#frames = 0; this.#startTime = performance.now(); this.frameRate = 0; this.ping = 0; - this.#id = uuidv4(); + this.#id = random(); // quick and dirty unique id my.stats = {}; + this.my = my; partySubscribe("ping", this.onPing.bind(this)); partySubscribe("pong", this.onPong.bind(this)); } @@ -26,7 +23,7 @@ class StatTracker { onPong(data) { if (data.sender === this.#id) { this.ping = performance.now() - data.time; - my.stats.ping = this.ping; + this.my.stats.ping = this.ping; } } tick() { @@ -37,7 +34,7 @@ class StatTracker { this.#frames = 0; this.#startTime = now; - my.stats.frameRate = this.frameRate; + this.my.stats.frameRate = this.frameRate; partyEmit("ping", { sender: this.#id, time: performance.now(), diff --git a/public/examples/tanks_1/README.md b/public/examples/tanks_1/README.md deleted file mode 100644 index bbe82c4..0000000 --- a/public/examples/tanks_1/README.md +++ /dev/null @@ -1,40 +0,0 @@ -# Tanks - -- **asdw** move tank -- **space** to fire - -> Try 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 separate 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. Conflicts are now much less likely, but still possible. - -This is starting to look a lot like message sending, and message sending would be a good way to fully fix the issue for this specific case. - -See tanks_emit for an example that uses message sending to solve this problem. diff --git a/public/examples/tanks_1/index.js b/public/examples/tanks_1/index.js deleted file mode 100644 index a21ae5c..0000000 --- a/public/examples/tanks_1/index.js +++ /dev/null @@ -1,129 +0,0 @@ -/* exported Rect */ -class Rect { - constructor(l = 0, t = 0, w = 0, h = 0) { - this.l = l; - this.t = t; - this.w = w; - this.h = h; - } -} - -function pointInRect(p, r) { - return p.x > r.l && p.x < r.l + r.w && p.y > r.t && p.y < r.t + r.h; -} - -const bounds = new Rect(0, 0, 400, 400); - -let shared; -let new_bullets; -let me; -let guests; - -function preload() { - partyConnect("wss://deepstream-server-1.herokuapp.com", "tanks_1", "main"); - shared = partyLoadShared("shared"); - new_bullets = partyLoadShared("new_bullets"); - me = partyLoadMyShared(); - guests = partyLoadGuestShareds(); -} - -function setup() { - createCanvas(400, 400).parent("canvas-wrap"); - - noStroke(); - - if (partyIsHost()) { - shared.bullets = []; - new_bullets.bullets = []; - } - - me.tank = { x: 100, y: 100, a: 0 }; -} - -function draw() { - checkKeys(); - if (partyIsHost()) stepGame(); - drawScene(); - showData(); -} - -function stepGame() { - // move sent bullets to main bullet array - while (new_bullets.bullets.length) { - shared.bullets.push(new_bullets.bullets.shift()); - } - - // step bullets - shared.bullets.forEach(stepBullet); -} - -function showData() { - document.getElementById("shared").innerText = JSON.stringify( - shared, - null, - "\t" - ); -} - -function drawScene() { - background("#cc6666"); - shared.bullets.forEach(drawBullet); - for (const p of guests) { - if (p.tank) drawTank(p.tank); - } -} - -function drawTank(tank) { - push(); - rectMode(CENTER); - translate(tank.x, tank.y); - rotate(tank.a); - rect(0, 0, 30, 30); - rect(0, -20, 5, 5); - pop(); -} - -function stepBullet(b) { - b.x += b.dX; - b.y += b.dY; - if (!pointInRect(b, bounds)) { - const i = shared.bullets.indexOf(b); - shared.bullets.splice(i, 1); - } -} - -function drawBullet(b) { - push(); - ellipse(b.x, b.y, 10, 10); - pop(); -} - -function keyPressed() { - if (key === " ") { - new_bullets.bullets.push({ - x: me.tank.x, - y: me.tank.y, - dX: sin(me.tank.a) * 6, - dY: -cos(me.tank.a) * 6, - }); - } - - return false; -} - -function checkKeys() { - // forward - if (keyIsDown(87) /*w*/) { - me.tank.x += sin(me.tank.a) * 3; - me.tank.y -= cos(me.tank.a) * 3; - } - - // backward - if (keyIsDown(83) /*s*/) { - me.tank.x += sin(me.tank.a) * -1; - me.tank.y -= cos(me.tank.a) * -1; - } - - if (keyIsDown(65) /*a*/) me.tank.a -= radians(2); - if (keyIsDown(68) /*d*/) me.tank.a += radians(2); -} diff --git a/public/examples/tanks_2/debug.js b/public/examples/tanks_2/debug.js deleted file mode 100644 index 1a1e558..0000000 --- a/public/examples/tanks_2/debug.js +++ /dev/null @@ -1,16 +0,0 @@ -/////////////////////////////////////////// -// DEBUG CODE - -/* exported debugShow */ -function debugShow(data) { - const roundIt = (key, value) => { - if (typeof value === "number") return Math.floor(value * 100) / 100; - return value; - }; - - document.getElementById("debug").innerText = JSON.stringify( - data, - roundIt, - " " - ); -} diff --git a/public/examples/tanks_2/index.html b/public/examples/tanks_2/index.html deleted file mode 100644 index 238458d..0000000 --- a/public/examples/tanks_2/index.html +++ /dev/null @@ -1,42 +0,0 @@ - - - - - - -
- -
- -

index.js

-
- -

index.html

-
- - - - - - - - - - - - - - - diff --git a/public/examples/tanks_2/shape.js b/public/examples/tanks_2/shape.js deleted file mode 100644 index 3d7d67c..0000000 --- a/public/examples/tanks_2/shape.js +++ /dev/null @@ -1,22 +0,0 @@ -/* exported Rect */ -class Rect { - constructor(l = 0, t = 0, w = 0, h = 0) { - this.l = l; - this.t = t; - this.w = w; - this.h = h; - } -} - -/* exported Point */ -class Point { - constructor(x = 0, y = 0) { - this.x = x; - this.y = y; - } -} - -/* exported pointInRect */ -function pointInRect(p, r) { - return p.x > r.l && p.x < r.l + r.w && p.y > r.t && p.y < r.t + r.h; -} diff --git a/public/index.html b/public/index.html index 71da31d..525b1c0 100644 --- a/public/index.html +++ b/public/index.html @@ -12,8 +12,8 @@

p5.Party Demos + Examples!!

- p5.party is a library for easily prototyping online multi-user sketches with p5.js. With p5.party you can quickly test ideas for multiplayer games, realtime multi-user - apps, and multi-computer art projects. + p5.party is a library for easily prototyping online multi-user sketches with p5.js. With p5.party you can quickly test ideas for multiplayer games, realtime multi-user apps, and multi-computer + art projects.

Github | @@ -48,7 +48,7 @@

Level 2

Level 3

-

Participants

+

Guests