├── .gitignore ├── .hgignore ├── LICENSE-MIT-AtomizeJS ├── README.md ├── app.js ├── bomberman ├── bomberman-compat.js ├── bomberman.js └── index.html ├── mongo-app.js ├── package.json └── test ├── onewriter-compat.js ├── onewriter.html ├── onewriter.js ├── queue-compat.js ├── queue.html ├── queue.js ├── retry-compat.js ├── retry.html ├── retry.js ├── unittests.html └── unittests.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | *~ 3 | -------------------------------------------------------------------------------- /.hgignore: -------------------------------------------------------------------------------- 1 | ~$ 2 | ^node_modules/ 3 | -------------------------------------------------------------------------------- /LICENSE-MIT-AtomizeJS: -------------------------------------------------------------------------------- 1 | Copyright (C) 2011-2012 VMware, Inc. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Atomize-examples 2 | 3 | This repository contains some tests and some examples of using 4 | AtomizeJS. 5 | 6 | The simplest way to get these up and running is: 7 | 8 | git clone https://github.com/atomizejs/atomize-examples.git 9 | npm install ./atomize-examples 10 | sh $(npm bin)/atomize-examples-server 11 | 12 | Please see the main [AtomizeJS site](http://atomizejs.github.com/) for 13 | further details. 14 | 15 | * test/ 16 | * unittests.html 17 | * Unit tests for the AtomizeJS client and server. 18 | * retry.html 19 | * Demonstrates using the `retry` functionality to wait for 20 | another client to join before setting a value. Watch the 21 | browser JavaScript console. 22 | * onewriter.html 23 | * Demonstrates many clients writing to the same variable 24 | safely. 25 | * queue.html 26 | * Demonstrates a broadcast queue: only one writer, but 27 | multiple readers, and every reader gets all messages added 28 | to the queue after the reader has joined. 29 | * bomberman/ 30 | * index.html 31 | * An implementation of the classic game 32 | [Bomberman](http://en.wikipedia.org/wiki/Bomberman). The 33 | entire multiplayer game is written in client-side JavaScript 34 | using just the AtomizeJS features to safely modify the game 35 | board. 36 | -------------------------------------------------------------------------------- /app.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /*global process, require */ 4 | /*jslint devel: true */ 5 | 6 | var express = require('express'); 7 | var http = require('http'); 8 | var atomize = require('atomize-server'); 9 | var path = require('path'); 10 | var app = express(); 11 | var httpServer = http.createServer(app); 12 | var port = 9999; 13 | var port_index = process.argv.indexOf('--port'); 14 | if (port_index > -1) { 15 | port = process.argv[port_index + 1]; 16 | } 17 | 18 | app.configure(function(){ 19 | app.use(express.logger('dev')); 20 | app.use(app.router); 21 | app.use(express.static(__dirname)); 22 | }); 23 | 24 | function serveJS (fileName, packageName) { 25 | var p; 26 | try { 27 | p = require.resolve(packageName); 28 | p = path.dirname(p); 29 | p = path.join(path.join(p, 'lib'), fileName); 30 | } catch (err) { 31 | p = require.resolve('atomize-server'); 32 | p = path.dirname(p); 33 | p = path.join( 34 | path.join( 35 | path.join( 36 | path.join(p, 'node_modules'), 37 | packageName), 38 | 'lib'), 39 | fileName); 40 | } 41 | app.get('/' + fileName, function (req, res) { 42 | res.sendfile(p); 43 | }); 44 | } 45 | 46 | serveJS('atomize.js', 'atomize-client'); 47 | serveJS('cereal.js', 'cereal'); 48 | serveJS('compat.js', 'atomize-client'); 49 | 50 | var atomizeServer = atomize.create(httpServer, '[/]atomize'); 51 | var atomizeClient = atomizeServer.client(); 52 | 53 | atomizeClient.atomically(function () { 54 | atomizeClient.root.clients = atomizeClient.lift({server: {}}); 55 | }, function () { 56 | atomizeServer.on('connection', function (client) { 57 | 58 | client.on('close', function (client) { 59 | atomizeClient.atomically(function () { 60 | delete atomizeClient.root.clients[client.connection.id]; 61 | }, function () { 62 | console.log("Connection death: " + client.connection.id); 63 | }); 64 | }); 65 | 66 | // Example of how to implement authentication 67 | /* client.on('data', function (message, client) { 68 | var text = JSON.parse(message).text; 69 | if (text === "wibble") { 70 | client.connection.write(JSON.stringify({text: "wobble"})); 71 | 72 | atomizeClient.atomically(function () { 73 | atomizeClient.root.clients[client.connection.id] = atomizeClient.lift({}); 74 | }, function () { 75 | console.log("New connection id: " + client.connection.id); 76 | client.isAuthenticated = true; 77 | }); 78 | } else { 79 | client.connection.write(JSON.stringify({text: "denied"})); 80 | } 81 | }); */ 82 | 83 | console.log("New connection id: " + client.connection.id); 84 | // disable this next line if you want to do some sort of real auth 85 | client.isAuthenticated = true; 86 | }); 87 | }); 88 | 89 | console.log(" [*] Listening on 0.0.0.0:" + port); 90 | httpServer.listen(port); 91 | -------------------------------------------------------------------------------- /bomberman/bomberman-compat.js: -------------------------------------------------------------------------------- 1 | // atomize-translate bomberman.js bomberman-compat.js atomize Bomberman Player Cell Bomb this 2 | 3 | var atomize, bomberman, canvas, ctx, clientWidth = 0, clientHeight = 0; 4 | function Cell (bomberman, x, y, raw) { 5 | var self = this; 6 | this.bomberman = bomberman; 7 | this.x = x; 8 | this.y = y; 9 | this.raw = raw; 10 | this.clearCount = 0; 11 | if (((((x === 0) || ((x + 1) === atomize.access(bomberman, "width"))) || (y === 0)) || ((y + 1) === atomize.access(bomberman, "height"))) || (((x % 2) === 0) && ((y % 2) === 0))) { 12 | this.wall = true; 13 | } 14 | atomize.atomically(function () { 15 | if (undefined === atomize.access(atomize.access(self, "raw"), "wall")) { 16 | atomize.assign(atomize.access(self, "raw"), "wall", atomize.access(self, "wall")); 17 | atomize.assign(atomize.access(self, "raw"), "fatal", atomize.access(self, "fatal")); 18 | } 19 | }); 20 | } 21 | Cell.prototype = {watching: false, wall: false, fatal: false, fatalTimer: 1000, setFatal: function () { 22 | var self, occupant, fun, bomb; 23 | self = this; 24 | atomize.atomically(function () { 25 | atomize.assign(atomize.access(self, "raw"), "fatal", true); 26 | occupant = atomize.access(atomize.access(self, "raw"), "occupant"); 27 | atomize.erase(atomize.access(self, "raw"), "occupant"); 28 | return occupant; 29 | }, function (occupant) { 30 | fun = function () { 31 | atomize.access(self, "clearFatal")(); 32 | }; 33 | setTimeout(fun, atomize.access(self, "fatalTimer")); 34 | atomize.assign(self, "clearCount", atomize.access(self, "clearCount") + 1); 35 | if (((undefined !== occupant) && ("bomb" === atomize.access(occupant, "type"))) && (undefined !== atomize.access(occupant, "id"))) { 36 | bomb = atomize.access(atomize.access(atomize.access(self, "bomberman"), "bombs"), atomize.access(occupant, "id")); 37 | if (undefined != bomb) { 38 | atomize.access(bomb, "explode")(); 39 | } 40 | } 41 | }); 42 | }, clearFatal: function () { 43 | var self = this; 44 | atomize.assign(self, "clearCount", atomize.access(self, "clearCount") - 1); 45 | if (atomize.access(self, "clearCount") === 0) { 46 | atomize.atomically(function () { 47 | atomize.assign(atomize.access(self, "raw"), "fatal", false); 48 | }); 49 | } 50 | }, render: function (ctx, scale) { 51 | var offset; 52 | if (this.wall) { 53 | atomize.access(ctx, "beginPath")(); 54 | atomize.assign(ctx, "fillStyle", "#000000"); 55 | atomize.access(ctx, "fillRect")(this.x * scale, this.y * scale, scale, scale); 56 | atomize.access(ctx, "closePath")(); 57 | } else 58 | if (this.fatal) { 59 | offset = (scale * 0.05); 60 | atomize.access(ctx, "beginPath")(); 61 | atomize.assign(ctx, "fillStyle", "#E00000"); 62 | atomize.access(ctx, "fillRect")(offset + (this.x * scale), offset + (this.y * scale), scale * 0.9, scale * 0.9); 63 | atomize.access(ctx, "closePath")(); 64 | } 65 | }, occupied: function () { 66 | return this.wall || (undefined !== this.occupant); 67 | }, placeBomb: function (bomb, cont) { 68 | var self = this; 69 | atomize.atomically(function () { 70 | if ((undefined === atomize.access(atomize.access(self, "raw"), "occupant")) || ("player" === atomize.access(atomize.access(atomize.access(self, "raw"), "occupant"), "type"))) { 71 | atomize.assign(atomize.access(self, "raw"), "occupant", atomize.access(bomb, "raw")); 72 | return true; 73 | } else { 74 | return false; 75 | } 76 | }, cont); 77 | }, occupy: function (player, cont) { 78 | var self = this; 79 | if (atomize.access(self, "wall")) { 80 | cont(false); 81 | } else { 82 | atomize.atomically(function () { 83 | if (atomize.access(atomize.access(self, "raw"), "fatal")) { 84 | return false; 85 | } else 86 | if (undefined === atomize.access(atomize.access(self, "raw"), "occupant")) { 87 | atomize.assign(atomize.access(self, "raw"), "occupant", atomize.access(player, "raw")); 88 | return true; 89 | } else { 90 | return false; 91 | } 92 | }, cont); 93 | } 94 | }, unoccupy: function (player) { 95 | var self = this; 96 | atomize.atomically(function () { 97 | if (atomize.access(atomize.access(self, "raw"), "occupant") === atomize.access(player, "raw")) { 98 | atomize.erase(atomize.access(self, "raw"), "occupant"); 99 | } 100 | }); 101 | }, watch: function () { 102 | var self, fun; 103 | if (this.wall || this.watching) { 104 | return; 105 | } 106 | this.watching = true; 107 | self = this; 108 | fun = function (props) { 109 | atomize.atomically(function () { 110 | if ((atomize.access(props, "occupant") === atomize.access(atomize.access(self, "raw"), "occupant")) && (atomize.access(props, "fatal") === atomize.access(atomize.access(self, "raw"), "fatal"))) { 111 | atomize.retry(); 112 | } else { 113 | return {occupant: atomize.access(atomize.access(self, "raw"), "occupant"), fatal: atomize.access(atomize.access(self, "raw"), "fatal")}; 114 | } 115 | }, function (props) { 116 | if (undefined === atomize.access(props, "occupant")) { 117 | atomize.erase(self, "occupant"); 118 | } else { 119 | atomize.assign(self, "occupant", atomize.access(props, "occupant")); 120 | } 121 | atomize.assign(self, "fatal", atomize.access(props, "fatal")); 122 | fun({occupant: atomize.access(self, "occupant"), fatal: atomize.access(self, "fatal")}); 123 | }); 124 | }; 125 | fun({occupant: atomize.access(self, "occupant"), fatal: atomize.access(self, "fatal")}); 126 | }}; 127 | function Bomb (bomberman, raw) { 128 | this.bomberman = bomberman; 129 | this.x = -1; 130 | this.y = -1; 131 | this.raw = raw; 132 | } 133 | Bomb.prototype = {exploded: false, timer: 1500, startTimer: function () { 134 | var self, explode; 135 | self = this; 136 | explode = function () { 137 | atomize.access(self, "explode")(); 138 | }; 139 | setTimeout(explode, atomize.access(self, "timer")); 140 | }, explode: function () { 141 | var self, exploded, i, cells; 142 | self = this; 143 | atomize.atomically(function () { 144 | var alreadyExploded = atomize.access(atomize.access(self, "raw"), "exploded"); 145 | atomize.assign(atomize.access(self, "raw"), "exploded", true); 146 | return alreadyExploded; 147 | }, function (alreadyExploded) { 148 | atomize.assign(self, "exploded", true); 149 | atomize.access(atomize.access(self, "bomberman"), "deleteBomb")(self); 150 | if (alreadyExploded) { 151 | return; 152 | } 153 | cells = [atomize.access(atomize.access(atomize.access(atomize.access(self, "bomberman"), "grid"), atomize.access(self, "x")), atomize.access(self, "y"))]; 154 | if (!atomize.access(atomize.access(atomize.access(atomize.access(atomize.access(self, "bomberman"), "grid"), atomize.access(self, "x")), atomize.access(self, "y") - 1), "wall")) { 155 | atomize.access(cells, "push")(atomize.access(atomize.access(atomize.access(atomize.access(self, "bomberman"), "grid"), atomize.access(self, "x")), atomize.access(self, "y") - 1)); 156 | if ((undefined !== atomize.access(atomize.access(atomize.access(atomize.access(self, "bomberman"), "grid"), atomize.access(self, "x")), atomize.access(self, "y") - 2)) && !atomize.access(atomize.access(atomize.access(atomize.access(atomize.access(self, "bomberman"), "grid"), atomize.access(self, "x")), atomize.access(self, "y") - 2), "wall")) { 157 | atomize.access(cells, "push")(atomize.access(atomize.access(atomize.access(atomize.access(self, "bomberman"), "grid"), atomize.access(self, "x")), atomize.access(self, "y") - 2)); 158 | } 159 | } 160 | if (!atomize.access(atomize.access(atomize.access(atomize.access(atomize.access(self, "bomberman"), "grid"), atomize.access(self, "x")), atomize.access(self, "y") + 1), "wall")) { 161 | atomize.access(cells, "push")(atomize.access(atomize.access(atomize.access(atomize.access(self, "bomberman"), "grid"), atomize.access(self, "x")), atomize.access(self, "y") + 1)); 162 | if ((undefined !== atomize.access(atomize.access(atomize.access(atomize.access(self, "bomberman"), "grid"), atomize.access(self, "x")), atomize.access(self, "y") + 2)) && !atomize.access(atomize.access(atomize.access(atomize.access(atomize.access(self, "bomberman"), "grid"), atomize.access(self, "x")), atomize.access(self, "y") + 2), "wall")) { 163 | atomize.access(cells, "push")(atomize.access(atomize.access(atomize.access(atomize.access(self, "bomberman"), "grid"), atomize.access(self, "x")), atomize.access(self, "y") + 2)); 164 | } 165 | } 166 | if (!atomize.access(atomize.access(atomize.access(atomize.access(atomize.access(self, "bomberman"), "grid"), atomize.access(self, "x") - 1), atomize.access(self, "y")), "wall")) { 167 | atomize.access(cells, "push")(atomize.access(atomize.access(atomize.access(atomize.access(self, "bomberman"), "grid"), atomize.access(self, "x") - 1), atomize.access(self, "y"))); 168 | if ((undefined !== atomize.access(atomize.access(atomize.access(self, "bomberman"), "grid"), atomize.access(self, "x") - 2)) && !atomize.access(atomize.access(atomize.access(atomize.access(atomize.access(self, "bomberman"), "grid"), atomize.access(self, "x") - 2), atomize.access(self, "y")), "wall")) { 169 | atomize.access(cells, "push")(atomize.access(atomize.access(atomize.access(atomize.access(self, "bomberman"), "grid"), atomize.access(self, "x") - 2), atomize.access(self, "y"))); 170 | } 171 | } 172 | if (!atomize.access(atomize.access(atomize.access(atomize.access(atomize.access(self, "bomberman"), "grid"), atomize.access(self, "x") + 1), atomize.access(self, "y")), "wall")) { 173 | atomize.access(cells, "push")(atomize.access(atomize.access(atomize.access(atomize.access(self, "bomberman"), "grid"), atomize.access(self, "x") + 1), atomize.access(self, "y"))); 174 | if ((undefined !== atomize.access(atomize.access(atomize.access(self, "bomberman"), "grid"), atomize.access(self, "x") + 2)) && !atomize.access(atomize.access(atomize.access(atomize.access(atomize.access(self, "bomberman"), "grid"), atomize.access(self, "x") + 2), atomize.access(self, "y")), "wall")) { 175 | atomize.access(cells, "push")(atomize.access(atomize.access(atomize.access(atomize.access(self, "bomberman"), "grid"), atomize.access(self, "x") + 2), atomize.access(self, "y"))); 176 | } 177 | } 178 | for (i = 0; i < atomize.access(cells, "length"); i += 1) { 179 | atomize.access(atomize.access(cells, i), "setFatal")(); 180 | } 181 | }); 182 | }, maybeInit: function () { 183 | var self = this; 184 | atomize.atomically(function () { 185 | return {x: atomize.access(atomize.access(self, "raw"), "x"), y: atomize.access(atomize.access(self, "raw"), "y")}; 186 | }, function (pos) { 187 | atomize.assign(self, "x", atomize.access(pos, "x")); 188 | atomize.assign(self, "y", atomize.access(pos, "y")); 189 | atomize.atomically(function () { 190 | if (undefined === atomize.access(atomize.access(self, "raw"), "id")) { 191 | atomize.retry(); 192 | } else { 193 | return atomize.access(atomize.access(self, "raw"), "id"); 194 | } 195 | }, function (id) { 196 | atomize.assign(self, "id", id); 197 | }); 198 | }); 199 | }, render: function (ctx, scale) { 200 | x = ((this.x + 0.5) * scale); 201 | y = ((this.y + 0.5) * scale); 202 | atomize.access(ctx, "beginPath")(); 203 | atomize.assign(ctx, "fillStyle", "#A00000"); 204 | atomize.access(ctx, "arc")(x, y, 0.45 * scale, 0, atomize.access(Math, "PI") * 2, true); 205 | atomize.access(ctx, "closePath")(); 206 | atomize.access(ctx, "fill")(); 207 | }}; 208 | function Player (bomberman, raw) { 209 | this.bomberman = bomberman; 210 | this.raw = raw; 211 | this.x = -1; 212 | this.y = -1; 213 | this.xCell = -1; 214 | this.yCell = -1; 215 | this.bombs = []; 216 | } 217 | Player.prototype = {watching: false, ready: false, dead: false, respawnTime: 5000, blocked: false, north: function () { 218 | this.xv = 0; 219 | this.yv = -0.1; 220 | }, south: function () { 221 | this.xv = 0; 222 | this.yv = 0.1; 223 | }, east: function () { 224 | this.xv = 0.1; 225 | this.yv = 0; 226 | }, west: function () { 227 | this.xv = -0.1; 228 | this.yv = 0; 229 | }, dropBomb: function () { 230 | var bombs, i, bomb, fun, self; 231 | bombs = []; 232 | for (i = 0; i < atomize.access(this.bombs, "length"); i += 1) { 233 | if (!atomize.access(atomize.access(this.bombs, i), "exploded")) { 234 | atomize.access(bombs, "push")(atomize.access(this.bombs, i)); 235 | } 236 | } 237 | this.bombs = bombs; 238 | if (atomize.access(this.bombs, "length") > 4) { 239 | return; 240 | } 241 | bomb = new Bomb(this.bomberman, atomize.lift({type: "bomb", x: this.xCell, y: this.yCell, exploded: false})); 242 | atomize.access(bomb, "maybeInit")(); 243 | self = this; 244 | fun = function (success) { 245 | if (success) { 246 | atomize.access(bomb, "startTimer")(); 247 | atomize.access(atomize.access(self, "bombs"), "push")(bomb); 248 | } 249 | } , atomize.access(this.bomberman, "dropBomb")(this.xCell, this.yCell, bomb, fun); 250 | }, render: function (ctx, scale) { 251 | var x, y; 252 | if (this.dead) { 253 | return; 254 | } 255 | x = (this.x * scale); 256 | y = (this.y * scale); 257 | atomize.access(ctx, "beginPath")(); 258 | if (this === atomize.access(this.bomberman, "me")) { 259 | atomize.assign(ctx, "fillStyle", "#00D0D0"); 260 | } else { 261 | atomize.assign(ctx, "fillStyle", "#00A000"); 262 | } 263 | atomize.access(ctx, "arc")(x, y, 0.25 * scale, 0, atomize.access(Math, "PI") * 2, true); 264 | atomize.access(ctx, "closePath")(); 265 | atomize.access(ctx, "fill")(); 266 | }, step: function () { 267 | var xNew, yNew, xCell, yCell, self, fun; 268 | if ((this.blocked || this.dead) || !this.ready) { 269 | return; 270 | } 271 | this.blocked = true; 272 | self = this; 273 | xNew = (this.x + this.xv); 274 | yNew = (this.y + this.yv); 275 | xCell = atomize.access(Math, "floor")(xNew); 276 | yCell = atomize.access(Math, "floor")(yNew); 277 | if (atomize.access(atomize.access(atomize.access(atomize.access(this.bomberman, "grid"), this.xCell), this.yCell), "fatal") || atomize.access(atomize.access(atomize.access(atomize.access(this.bomberman, "grid"), xCell), yCell), "fatal")) { 278 | atomize.atomically(function () { 279 | atomize.assign(atomize.access(self, "raw"), "dead", true); 280 | atomize.access(atomize.access(self, "bomberman"), "unoccupy")(atomize.access(self, "xCell"), atomize.access(self, "yCell"), self); 281 | }, function () { 282 | atomize.assign(self, "dead", true); 283 | atomize.assign(self, "blocked", false); 284 | fun = function () { 285 | atomize.access(self, "spawn")(); 286 | }; 287 | setTimeout(fun, atomize.access(self, "respawnTime")); 288 | }); 289 | return; 290 | } 291 | if ((xCell !== this.xCell) || (yCell !== this.yCell)) { 292 | if (atomize.access(atomize.access(atomize.access(atomize.access(this.bomberman, "grid"), xCell), yCell), "occupied")()) { 293 | this.blocked = false; 294 | return; 295 | } else { 296 | self = this; 297 | fun = function (success) { 298 | if (success) { 299 | atomize.access(atomize.access(self, "bomberman"), "unoccupy")(atomize.access(self, "xCell"), atomize.access(self, "yCell"), self); 300 | atomize.assign(self, "xCell", xCell); 301 | atomize.assign(self, "yCell", yCell); 302 | atomize.assign(self, "x", xNew); 303 | atomize.assign(self, "y", yNew); 304 | atomize.atomically(function () { 305 | atomize.assign(atomize.access(self, "raw"), "x", xNew); 306 | atomize.assign(atomize.access(self, "raw"), "y", yNew); 307 | }, function () { 308 | atomize.assign(self, "blocked", false); 309 | }); 310 | } 311 | }; 312 | atomize.access(this.bomberman, "occupy")(xCell, yCell, self, fun); 313 | } 314 | } else { 315 | atomize.atomically(function () { 316 | atomize.assign(atomize.access(self, "raw"), "x", xNew); 317 | atomize.assign(atomize.access(self, "raw"), "y", yNew); 318 | }, function () { 319 | atomize.assign(self, "x", xNew); 320 | atomize.assign(self, "y", yNew); 321 | atomize.assign(self, "blocked", false); 322 | }); 323 | } 324 | }, watch: function () { 325 | var self, fun; 326 | if (this.watching) { 327 | return; 328 | } 329 | this.watching = true; 330 | self = this; 331 | fun = function () { 332 | atomize.atomically(function () { 333 | if (((atomize.access(self, "x") === atomize.access(atomize.access(self, "raw"), "x")) && (atomize.access(self, "y") === atomize.access(atomize.access(self, "raw"), "y"))) && (atomize.access(self, "dead") === atomize.access(atomize.access(self, "raw"), "dead"))) { 334 | atomize.retry(); 335 | } else { 336 | return {x: atomize.access(atomize.access(self, "raw"), "x"), y: atomize.access(atomize.access(self, "raw"), "y"), dead: atomize.access(atomize.access(self, "raw"), "dead")}; 337 | } 338 | }, function (pos) { 339 | atomize.assign(self, "x", atomize.access(pos, "x")); 340 | atomize.assign(self, "y", atomize.access(pos, "y")); 341 | atomize.assign(self, "dead", atomize.access(pos, "dead")); 342 | fun(); 343 | }); 344 | }; 345 | fun(); 346 | }, spawn: function () { 347 | var self, fun, x, y, directions, keys; 348 | self = this; 349 | x = atomize.access(Math, "round")((atomize.access(atomize.access(self, "bomberman"), "width") - 1) * atomize.access(Math, "random")()); 350 | y = atomize.access(Math, "round")((atomize.access(atomize.access(self, "bomberman"), "height") - 1) * atomize.access(Math, "random")()); 351 | fun = function (success) { 352 | if (success) { 353 | atomize.atomically(function () { 354 | atomize.assign(atomize.access(self, "raw"), "x", x + 0.5); 355 | atomize.assign(atomize.access(self, "raw"), "y", y + 0.5); 356 | atomize.assign(atomize.access(self, "raw"), "dead", false); 357 | }, function () { 358 | atomize.assign(self, "x", x + 0.5); 359 | atomize.assign(self, "y", y + 0.5); 360 | atomize.assign(self, "xCell", x); 361 | atomize.assign(self, "yCell", y); 362 | atomize.assign(self, "ready", true); 363 | atomize.assign(self, "dead", false); 364 | directions = {north: function () { 365 | atomize.access(self, "north")(); 366 | }, south: function () { 367 | atomize.access(self, "south")(); 368 | }, east: function () { 369 | atomize.access(self, "east")(); 370 | }, west: function () { 371 | atomize.access(self, "west")(); 372 | }}; 373 | if (atomize.access(atomize.access(atomize.access(atomize.access(atomize.access(self, "bomberman"), "grid"), x - 1), y), "wall")) { 374 | atomize.erase(directions, "west"); 375 | } 376 | if (atomize.access(atomize.access(atomize.access(atomize.access(atomize.access(self, "bomberman"), "grid"), x + 1), y), "wall")) { 377 | atomize.erase(directions, "east"); 378 | } 379 | if (atomize.access(atomize.access(atomize.access(atomize.access(atomize.access(self, "bomberman"), "grid"), x), y - 1), "wall")) { 380 | atomize.erase(directions, "north"); 381 | } 382 | if (atomize.access(atomize.access(atomize.access(atomize.access(atomize.access(self, "bomberman"), "grid"), x), y + 1), "wall")) { 383 | atomize.erase(directions, "south"); 384 | } 385 | keys = atomize.access(Object, "keys")(directions); 386 | atomize.access(directions, atomize.access(keys, atomize.access(Math, "round")((atomize.access(keys, "length") - 1) * atomize.access(Math, "random")())))(); 387 | }); 388 | } else { 389 | atomize.access(self, "spawn")(); 390 | } 391 | }; 392 | atomize.access(atomize.access(self, "bomberman"), "occupy")(x, y, self, fun); 393 | }}; 394 | function Bomberman (raw) { 395 | this.raw = raw; 396 | this.grid = []; 397 | this.players = {}; 398 | this.bombs = {}; 399 | } 400 | Bomberman.prototype = {width: 25, height: 25, dropBomb: function (x, y, bomb, cont) { 401 | var self, fun; 402 | self = this; 403 | fun = function (success) { 404 | if (success) { 405 | atomize.atomically(function () { 406 | atomize.assign(atomize.access(atomize.access(self, "raw"), "bombs"), "eventCount", atomize.access(atomize.access(atomize.access(self, "raw"), "bombs"), "eventCount") + 1); 407 | atomize.assign(atomize.access(atomize.access(atomize.access(self, "raw"), "bombs"), "bombs"), atomize.access(atomize.access(atomize.access(self, "raw"), "bombs"), "eventCount"), atomize.access(bomb, "raw")); 408 | atomize.assign(atomize.access(bomb, "raw"), "id", atomize.access(atomize.access(atomize.access(self, "raw"), "bombs"), "eventCount")); 409 | return true; 410 | }, cont); 411 | } else { 412 | cont(false); 413 | } 414 | }; 415 | atomize.access(atomize.access(atomize.access(atomize.access(self, "grid"), x), y), "placeBomb")(bomb, fun); 416 | }, deleteBomb: function (bomb) { 417 | var self = this; 418 | atomize.atomically(function () { 419 | if (atomize.access(bomb, "raw") === atomize.access(atomize.access(atomize.access(atomize.access(self, "raw"), "bombs"), "bombs"), atomize.access(bomb, "id"))) { 420 | atomize.assign(atomize.access(atomize.access(self, "raw"), "bombs"), "eventCount", atomize.access(atomize.access(atomize.access(self, "raw"), "bombs"), "eventCount") + 1); 421 | atomize.erase(atomize.access(atomize.access(atomize.access(self, "raw"), "bombs"), "bombs"), atomize.access(bomb, "id")); 422 | } 423 | }); 424 | }, occupy: function (x, y, player, cont) { 425 | atomize.access(atomize.access(atomize.access(this.grid, x), y), "occupy")(player, cont); 426 | }, unoccupy: function (x, y, player) { 427 | atomize.access(atomize.access(atomize.access(this.grid, x), y), "unoccupy")(player); 428 | }, watchGrid: function () { 429 | var x, y; 430 | for (x = 0; x < atomize.access(this.grid, "length"); x += 1) { 431 | for (y = 0; y < atomize.access(atomize.access(this.grid, x), "length"); y += 1) { 432 | atomize.access(atomize.access(atomize.access(this.grid, x), y), "watch")(); 433 | } 434 | } 435 | }, watchPlayers: function () { 436 | var fun, self, players, keys, i; 437 | self = this; 438 | fun = function (eventCount) { 439 | atomize.atomically(function () { 440 | if (atomize.access(atomize.access(atomize.access(self, "raw"), "players"), "eventCount") === eventCount) { 441 | atomize.retry(); 442 | } else { 443 | players = {}; 444 | keys = atomize.access(Object, "keys")(atomize.access(atomize.access(atomize.access(self, "raw"), "players"), "players")); 445 | for (i = 0; i < atomize.access(keys, "length"); i += 1) { 446 | atomize.assign(players, atomize.access(keys, i), atomize.access(atomize.access(atomize.access(atomize.access(self, "raw"), "players"), "players"), atomize.access(keys, i))); 447 | } 448 | return {players: players, eventCount: atomize.access(atomize.access(atomize.access(self, "raw"), "players"), "eventCount")}; 449 | } 450 | }, function (result) { 451 | atomize.assign(self, "players", {}); 452 | keys = atomize.access(Object, "keys")(atomize.access(result, "players")); 453 | for (i = 0; i < atomize.access(keys, "length"); i += 1) { 454 | if (atomize.access(atomize.access(result, "players"), atomize.access(keys, i)) === atomize.access(atomize.access(self, "me"), "raw")) { 455 | atomize.assign(atomize.access(self, "players"), atomize.access(keys, i), atomize.access(self, "me")); 456 | } else { 457 | atomize.assign(atomize.access(self, "players"), atomize.access(keys, i), new Player(self, atomize.access(atomize.access(result, "players"), atomize.access(keys, i)))); 458 | atomize.access(atomize.access(atomize.access(self, "players"), atomize.access(keys, i)), "watch")(); 459 | } 460 | } 461 | fun(atomize.access(result, "eventCount")); 462 | }); 463 | }; 464 | fun(0); 465 | }, watchBombs: function () { 466 | var fun, self, bombs, keys, i; 467 | self = this; 468 | fun = function (eventCount) { 469 | atomize.atomically(function () { 470 | if (atomize.access(atomize.access(atomize.access(self, "raw"), "bombs"), "eventCount") === eventCount) { 471 | atomize.retry(); 472 | } else { 473 | bombs = {}; 474 | keys = atomize.access(Object, "keys")(atomize.access(atomize.access(atomize.access(self, "raw"), "bombs"), "bombs")); 475 | for (i = 0; i < atomize.access(keys, "length"); i += 1) { 476 | atomize.assign(bombs, atomize.access(keys, i), atomize.access(atomize.access(atomize.access(atomize.access(self, "raw"), "bombs"), "bombs"), atomize.access(keys, i))); 477 | } 478 | return {bombs: bombs, eventCount: atomize.access(atomize.access(atomize.access(self, "raw"), "bombs"), "eventCount")}; 479 | } 480 | }, function (result) { 481 | atomize.assign(self, "bombs", {}); 482 | keys = atomize.access(Object, "keys")(atomize.access(result, "bombs")); 483 | for (i = 0; i < atomize.access(keys, "length"); i += 1) { 484 | atomize.assign(atomize.access(self, "bombs"), atomize.access(keys, i), new Bomb(self, atomize.access(atomize.access(result, "bombs"), atomize.access(keys, i)))); 485 | atomize.access(atomize.access(atomize.access(self, "bombs"), atomize.access(keys, i)), "maybeInit")(); 486 | } 487 | fun(atomize.access(result, "eventCount")); 488 | }); 489 | }; 490 | fun(0); 491 | }, maybeInit: function () { 492 | var self, x, y, raw, cell; 493 | self = this; 494 | atomize.atomically(function () { 495 | if (undefined === atomize.access(atomize.access(self, "raw"), "players")) { 496 | atomize.assign(atomize.access(self, "raw"), "players", atomize.lift({eventCount: 0, players: {}})); 497 | } 498 | if (undefined === atomize.access(atomize.access(self, "raw"), "bombs")) { 499 | atomize.assign(atomize.access(self, "raw"), "bombs", atomize.lift({eventCount: 0, bombs: {}})); 500 | } 501 | if (undefined === atomize.access(atomize.access(self, "raw"), "grid")) { 502 | atomize.assign(atomize.access(self, "raw"), "grid", atomize.lift([])); 503 | for (x = 0; x < atomize.access(self, "width"); x += 1) { 504 | atomize.assign(atomize.access(atomize.access(self, "raw"), "grid"), x, atomize.lift([])); 505 | atomize.assign(atomize.access(self, "grid"), x, []); 506 | for (y = 0; y < atomize.access(self, "height"); y += 1) { 507 | raw = atomize.lift({}); 508 | atomize.assign(atomize.access(atomize.access(atomize.access(self, "raw"), "grid"), x), y, raw); 509 | cell = new Cell(self, x, y, raw); 510 | atomize.assign(atomize.access(atomize.access(self, "grid"), x), y, cell); 511 | } 512 | } 513 | } else { 514 | atomize.assign(self, "width", atomize.access(atomize.access(atomize.access(self, "raw"), "grid"), "length")); 515 | atomize.assign(self, "height", atomize.access(atomize.access(atomize.access(atomize.access(self, "raw"), "grid"), 0), "length")); 516 | for (x = 0; x < atomize.access(self, "width"); x += 1) { 517 | atomize.assign(atomize.access(self, "grid"), x, []); 518 | for (y = 0; y < atomize.access(self, "height"); y += 1) { 519 | cell = new Cell(self, x, y, atomize.access(atomize.access(atomize.access(atomize.access(self, "raw"), "grid"), x), y)); 520 | atomize.assign(atomize.access(atomize.access(self, "grid"), x), y, cell); 521 | } 522 | } 523 | } 524 | return atomize.lift({type: "player", dead: false}); 525 | }, function (me) { 526 | atomize.assign(self, "me", new Player(self, me)); 527 | atomize.access(self, "watchGrid")(); 528 | atomize.access(self, "watchPlayers")(); 529 | atomize.access(self, "watchBombs")(); 530 | atomize.atomically(function () { 531 | atomize.assign(atomize.access(atomize.access(self, "raw"), "players"), "eventCount", atomize.access(atomize.access(atomize.access(self, "raw"), "players"), "eventCount") + 1); 532 | atomize.assign(atomize.access(atomize.access(atomize.access(self, "raw"), "players"), "players"), atomize.access(atomize.access(atomize.access(self, "raw"), "players"), "eventCount"), me); 533 | atomize.assign(atomize.access(atomize.access(self, "me"), "raw"), "id", atomize.access(atomize.access(atomize.access(self, "raw"), "players"), "eventCount")); 534 | }, function () { 535 | atomize.access(atomize.access(self, "me"), "spawn")(); 536 | }); 537 | }); 538 | }, render: function (ctx) { 539 | var minDim, maxDim, wallLen, x, y, keys; 540 | minDim = atomize.access(Math, "min")(clientWidth, clientHeight); 541 | maxDim = atomize.access(Math, "max")(this.width, this.height); 542 | wallLen = (minDim / maxDim); 543 | for (x = 0; x < atomize.access(this.grid, "length"); x += 1) { 544 | for (y = 0; y < atomize.access(atomize.access(this.grid, x), "length"); y += 1) { 545 | atomize.access(atomize.access(atomize.access(this.grid, x), y), "render")(ctx, wallLen); 546 | } 547 | } 548 | keys = atomize.access(Object, "keys")(this.players); 549 | for (x = 0; x < atomize.access(keys, "length"); x += 1) { 550 | atomize.access(atomize.access(this.players, atomize.access(keys, x)), "render")(ctx, wallLen); 551 | } 552 | keys = atomize.access(Object, "keys")(this.bombs); 553 | for (x = 0; x < atomize.access(keys, "length"); x += 1) { 554 | atomize.access(atomize.access(this.bombs, atomize.access(keys, x)), "render")(ctx, wallLen); 555 | } 556 | }}; 557 | function resizeCanvas () { 558 | var e; 559 | if (undefined !== canvas) { 560 | atomize.assign(canvas, "width", atomize.access(atomize.access(canvas, "parentNode"), "offsetWidth")); 561 | atomize.assign(canvas, "height", atomize.access(atomize.access(canvas, "parentNode"), "offsetHeight")); 562 | clientWidth = atomize.access(canvas, "width"); 563 | clientHeight = atomize.access(canvas, "height"); 564 | e = atomize.access(canvas, "parentNode"); 565 | while ((undefined !== e) && (null !== e)) { 566 | if ((((undefined !== atomize.access(e, "clientHeight")) && (undefined !== atomize.access(e, "clientWidth"))) && (atomize.access(e, "clientHeight") > 0)) && (atomize.access(e, "clientWidth") > 0)) { 567 | clientHeight = atomize.access(Math, "min")(clientHeight, atomize.access(e, "clientHeight")); 568 | clientWidth = atomize.access(Math, "min")(clientWidth, atomize.access(e, "clientWidth")); 569 | } 570 | e = atomize.access(e, "parentNode"); 571 | } 572 | canvasLeft = 10; 573 | canvasTop = 10; 574 | e = atomize.access(canvas, "parentNode"); 575 | while ((undefined !== e) && (null !== e)) { 576 | if ((undefined !== atomize.access(e, "offsetLeft")) && (undefined !== atomize.access(e, "offsetTop"))) { 577 | canvasLeft += atomize.access(e, "offsetLeft"); 578 | canvasTop += atomize.access(e, "offsetTop"); 579 | } 580 | e = atomize.access(e, "parentNode"); 581 | } 582 | } 583 | } 584 | function initCanvas () { 585 | resizeCanvas(); 586 | try { 587 | ctx = atomize.access(canvas, "getContext")("2d"); 588 | } catch (e) { 589 | } 590 | if (!ctx) { 591 | alert("Could not initialise 2D canvas. Change browser?"); 592 | } 593 | } 594 | function drawScene () { 595 | atomize.access(ctx, "clearRect")(0, 0, clientWidth, clientHeight); 596 | atomize.assign(ctx, "lineWidth", 1); 597 | atomize.assign(ctx, "lineCap", "round"); 598 | atomize.assign(ctx, "lineJoin", "round"); 599 | atomize.assign(ctx, "strokeStyle", "black"); 600 | atomize.access(bomberman, "render")(ctx); 601 | } 602 | requestAnimFrame = (function () { 603 | return ((((this.requestAnimationFrame || this.webkitRequestAnimationFrame) || this.mozRequestAnimationFrame) || this.oRequestAnimationFrame) || this.msRequestAnimationFrame) || function (callback, element) { 604 | setTimeout(callback, 1000 / 60); 605 | } 606 | }()); 607 | function tick () { 608 | if ((undefined !== bomberman) && (undefined !== atomize.access(bomberman, "me"))) { 609 | atomize.access(atomize.access(bomberman, "me"), "step")(); 610 | } 611 | drawScene(); 612 | requestAnimFrame(tick); 613 | } 614 | function doKeyDown (event) { 615 | switch (atomize.access(event, "keyCode")) { 616 | case 38: 617 | atomize.access(atomize.access(bomberman, "me"), "north")(); 618 | break; 619 | 620 | case 40: 621 | atomize.access(atomize.access(bomberman, "me"), "south")(); 622 | break; 623 | 624 | case 37: 625 | atomize.access(atomize.access(bomberman, "me"), "west")(); 626 | break; 627 | 628 | case 39: 629 | atomize.access(atomize.access(bomberman, "me"), "east")(); 630 | break; 631 | 632 | case 32: 633 | atomize.access(atomize.access(bomberman, "me"), "dropBomb")(); 634 | break; 635 | 636 | } 637 | } 638 | function init () { 639 | atomize = new Atomize(); 640 | canvas = atomize.access(document, "getElementById")("game_canvas"); 641 | initCanvas(); 642 | atomize.onAuthenticated = function () { 643 | atomize.atomically(function () { 644 | if (undefined === atomize.access(atomize.root, "bomberman")) { 645 | atomize.assign(atomize.root, "bomberman", atomize.lift({})); 646 | } 647 | return atomize.access(atomize.root, "bomberman"); 648 | }, function (raw) { 649 | bomberman = new Bomberman(raw); 650 | atomize.access(bomberman, "maybeInit")(); 651 | requestAnimFrame(tick); 652 | atomize.access(window, "addEventListener")("keydown", doKeyDown, true); 653 | }); 654 | }; 655 | atomize.connect(); 656 | } 657 | -------------------------------------------------------------------------------- /bomberman/bomberman.js: -------------------------------------------------------------------------------- 1 | // atomize-translate bomberman.js bomberman-compat.js atomize Bomberman Player Cell Bomb this 2 | 3 | var atomize, 4 | bomberman, 5 | canvas, 6 | ctx, 7 | clientWidth = 0, 8 | clientHeight = 0; 9 | 10 | function Cell(bomberman, x, y, raw) { 11 | var self = this; 12 | this.bomberman = bomberman; 13 | this.x = x; 14 | this.y = y; 15 | this.raw = raw; 16 | this.clearCount = 0; 17 | if (x === 0 || x + 1 === bomberman.width || 18 | y === 0 || y + 1 === bomberman.height || 19 | (x % 2 === 0 && y % 2 === 0)) { 20 | this.wall = true; 21 | } 22 | atomize.atomically(function () { 23 | if (undefined === self.raw.wall) { 24 | self.raw.wall = self.wall; 25 | self.raw.fatal = self.fatal; 26 | } 27 | }); 28 | } 29 | 30 | Cell.prototype = { 31 | watching: false, 32 | wall: false, 33 | fatal: false, 34 | fatalTimer: 1000, 35 | 36 | setFatal: function () { 37 | var self, occupant, fun, bomb; 38 | self = this; 39 | atomize.atomically(function () { 40 | self.raw.fatal = true; 41 | occupant = self.raw.occupant; 42 | delete self.raw.occupant; 43 | return occupant; 44 | }, function (occupant) { 45 | fun = function () { self.clearFatal(); }; 46 | setTimeout(fun, self.fatalTimer); 47 | self.clearCount += 1; 48 | if (undefined !== occupant && "bomb" === occupant.type && undefined !== occupant.id) { 49 | bomb = self.bomberman.bombs[occupant.id]; 50 | if (undefined != bomb) { 51 | bomb.explode(); 52 | } 53 | } 54 | }); 55 | }, 56 | 57 | clearFatal: function () { 58 | var self = this; 59 | self.clearCount -= 1; 60 | if (self.clearCount === 0) { 61 | atomize.atomically(function () { 62 | self.raw.fatal = false; 63 | }); 64 | } 65 | }, 66 | 67 | render: function (ctx, scale) { 68 | var offset; 69 | if (this.wall) { 70 | ctx.beginPath(); 71 | ctx.fillStyle="#000000"; 72 | ctx.fillRect(this.x*scale, this.y*scale, scale, scale); 73 | ctx.closePath(); 74 | } else if (this.fatal) { 75 | offset = scale*0.05; 76 | ctx.beginPath(); 77 | ctx.fillStyle="#E00000"; 78 | ctx.fillRect(offset + this.x*scale, offset + this.y*scale, scale*0.9, scale*0.9); 79 | ctx.closePath(); 80 | } 81 | }, 82 | 83 | occupied: function () { 84 | return this.wall || undefined !== this.occupant; 85 | }, 86 | 87 | placeBomb: function (bomb, cont) { 88 | var self = this; 89 | atomize.atomically(function () { 90 | if (undefined === self.raw.occupant || 91 | "player" === self.raw.occupant.type) { 92 | self.raw.occupant = bomb.raw; 93 | return true; 94 | } else { 95 | return false; 96 | } 97 | }, cont); 98 | }, 99 | 100 | occupy: function (player, cont) { 101 | var self = this; 102 | if (self.wall) { 103 | cont(false); 104 | } else { 105 | atomize.atomically(function () { 106 | if (self.raw.fatal) { 107 | return false; 108 | } else if (undefined === self.raw.occupant) { 109 | self.raw.occupant = player.raw; 110 | return true; 111 | } else { 112 | return false; 113 | } 114 | }, cont); 115 | } 116 | }, 117 | 118 | unoccupy: function (player) { 119 | var self = this; 120 | atomize.atomically(function () { 121 | if (self.raw.occupant === player.raw) { 122 | delete self.raw.occupant; 123 | } 124 | }); 125 | }, 126 | 127 | watch: function () { 128 | var self, fun; 129 | if (this.wall || this.watching) { 130 | return; 131 | } 132 | this.watching = true; 133 | self = this; 134 | fun = function (props) { 135 | atomize.atomically(function () { 136 | if (props.occupant === self.raw.occupant && 137 | props.fatal === self.raw.fatal) { 138 | atomize.retry(); 139 | } else { 140 | return {occupant: self.raw.occupant, 141 | fatal: self.raw.fatal}; 142 | } 143 | }, function (props) { 144 | if (undefined === props.occupant) { 145 | delete self.occupant; 146 | } else { 147 | self.occupant = props.occupant; 148 | } 149 | self.fatal = props.fatal; 150 | fun({occupant: self.occupant, fatal: self.fatal}); 151 | }) 152 | }; 153 | fun({occupant: self.occupant, fatal: self.fatal}); 154 | } 155 | }; 156 | 157 | function Bomb(bomberman, raw) { 158 | this.bomberman = bomberman; 159 | this.x = -1; 160 | this.y = -1; 161 | this.raw = raw; 162 | } 163 | 164 | Bomb.prototype = { 165 | exploded: false, 166 | timer: 1500, 167 | 168 | startTimer: function () { 169 | var self, explode; 170 | self = this; 171 | explode = function () { 172 | self.explode(); 173 | } 174 | setTimeout(explode, self.timer); 175 | }, 176 | 177 | explode: function () { 178 | var self, exploded, i, cells; 179 | self = this; 180 | atomize.atomically(function () { 181 | var alreadyExploded = self.raw.exploded; 182 | self.raw.exploded = true; 183 | return alreadyExploded; 184 | }, function (alreadyExploded) { 185 | self.exploded = true; 186 | self.bomberman.deleteBomb(self); 187 | if (alreadyExploded) { 188 | return; 189 | } 190 | cells = [self.bomberman.grid[self.x][self.y]]; 191 | 192 | if (! self.bomberman.grid[self.x][self.y - 1].wall) { 193 | cells.push(self.bomberman.grid[self.x][self.y - 1]); 194 | if (undefined !== self.bomberman.grid[self.x][self.y - 2] && 195 | ! self.bomberman.grid[self.x][self.y - 2].wall) { 196 | cells.push(self.bomberman.grid[self.x][self.y - 2]); 197 | } 198 | } 199 | if (! self.bomberman.grid[self.x][self.y + 1].wall) { 200 | cells.push(self.bomberman.grid[self.x][self.y + 1]); 201 | if (undefined !== self.bomberman.grid[self.x][self.y + 2] && 202 | ! self.bomberman.grid[self.x][self.y + 2].wall) { 203 | cells.push(self.bomberman.grid[self.x][self.y + 2]); 204 | } 205 | } 206 | 207 | if (! self.bomberman.grid[self.x - 1][self.y].wall) { 208 | cells.push(self.bomberman.grid[self.x - 1][self.y]); 209 | if (undefined !== self.bomberman.grid[self.x - 2] && 210 | ! self.bomberman.grid[self.x - 2][self.y].wall) { 211 | cells.push(self.bomberman.grid[self.x - 2][self.y]); 212 | } 213 | } 214 | if (! self.bomberman.grid[self.x + 1][self.y].wall) { 215 | cells.push(self.bomberman.grid[self.x + 1][self.y]); 216 | if (undefined !== self.bomberman.grid[self.x + 2] && 217 | ! self.bomberman.grid[self.x + 2][self.y].wall) { 218 | cells.push(self.bomberman.grid[self.x + 2][self.y]); 219 | } 220 | } 221 | 222 | for (i = 0; i < cells.length; i += 1) { 223 | cells[i].setFatal(); 224 | } 225 | }); 226 | }, 227 | 228 | maybeInit: function () { 229 | var self = this; 230 | atomize.atomically(function () { 231 | return {x: self.raw.x, y: self.raw.y} 232 | }, function (pos) { 233 | self.x = pos.x; 234 | self.y = pos.y; 235 | atomize.atomically(function () { 236 | if (undefined === self.raw.id) { 237 | atomize.retry(); 238 | } else { 239 | return self.raw.id; 240 | } 241 | }, function (id) { 242 | self.id = id; 243 | }); 244 | }); 245 | }, 246 | 247 | render: function (ctx, scale) { 248 | x = (this.x + 0.5) * scale; 249 | y = (this.y + 0.5) * scale; 250 | ctx.beginPath(); 251 | ctx.fillStyle="#A00000"; 252 | ctx.arc(x,y,0.45*scale,0,Math.PI*2,true); 253 | ctx.closePath(); 254 | ctx.fill(); 255 | } 256 | } 257 | 258 | function Player(bomberman, raw) { 259 | this.bomberman = bomberman; 260 | this.raw = raw; 261 | this.x = -1; 262 | this.y = -1; 263 | this.xCell = -1; 264 | this.yCell = -1; 265 | this.bombs = []; 266 | } 267 | 268 | Player.prototype = { 269 | watching: false, 270 | ready: false, 271 | dead: false, 272 | respawnTime: 5000, 273 | blocked: false, 274 | 275 | north: function () { 276 | this.xv = 0; 277 | this.yv = -0.1; 278 | }, 279 | 280 | south: function () { 281 | this.xv = 0; 282 | this.yv = 0.1; 283 | }, 284 | 285 | east: function () { 286 | this.xv = 0.1; 287 | this.yv = 0; 288 | }, 289 | 290 | west: function () { 291 | this.xv = -0.1; 292 | this.yv = 0; 293 | }, 294 | 295 | dropBomb: function () { 296 | var bombs, i, bomb, fun, self; 297 | bombs = []; 298 | for (i = 0; i < this.bombs.length; i += 1) { 299 | if (! this.bombs[i].exploded) { 300 | bombs.push(this.bombs[i]); 301 | } 302 | } 303 | this.bombs = bombs; 304 | if (this.bombs.length > 4) { 305 | return; 306 | } 307 | bomb = new Bomb(this.bomberman, atomize.lift({type: "bomb", 308 | x: this.xCell, 309 | y: this.yCell, 310 | exploded: false})); 311 | bomb.maybeInit(); 312 | self = this; 313 | fun = function (success) { 314 | if (success) { 315 | bomb.startTimer(); 316 | self.bombs.push(bomb); 317 | } 318 | }, 319 | this.bomberman.dropBomb(this.xCell, this.yCell, bomb, fun); 320 | }, 321 | 322 | render: function (ctx, scale) { 323 | var x, y; 324 | if (this.dead) { 325 | return; 326 | } 327 | x = this.x * scale; 328 | y = this.y * scale; 329 | ctx.beginPath(); 330 | if (this === this.bomberman.me) { 331 | ctx.fillStyle="#00D0D0"; 332 | } else { 333 | ctx.fillStyle="#00A000"; 334 | } 335 | ctx.arc(x, y, 0.25*scale, 0, Math.PI*2, true); 336 | ctx.closePath(); 337 | ctx.fill(); 338 | }, 339 | 340 | step: function () { 341 | var xNew, yNew, xCell, yCell, self, fun; 342 | if (this.blocked || this.dead || ! this.ready) { 343 | return; 344 | } 345 | this.blocked = true; 346 | self = this; 347 | xNew = this.x + this.xv; 348 | yNew = this.y + this.yv; 349 | xCell = Math.floor(xNew); 350 | yCell = Math.floor(yNew); 351 | if (this.bomberman.grid[this.xCell][this.yCell].fatal || 352 | this.bomberman.grid[xCell][yCell].fatal) { 353 | atomize.atomically(function () { 354 | self.raw.dead = true; 355 | self.bomberman.unoccupy(self.xCell, self.yCell, self); 356 | }, function () { 357 | self.dead = true; 358 | self.blocked = false; 359 | fun = function () { 360 | self.spawn(); 361 | }; 362 | setTimeout(fun, self.respawnTime); 363 | }); 364 | return; 365 | } 366 | if (xCell !== this.xCell || yCell !== this.yCell) { 367 | if (this.bomberman.grid[xCell][yCell].occupied()) { 368 | this.blocked = false; 369 | return; 370 | } else { 371 | self = this; 372 | fun = function (success) { 373 | if (success) { 374 | self.bomberman.unoccupy(self.xCell, self.yCell, self); 375 | self.xCell = xCell; 376 | self.yCell = yCell; 377 | self.x = xNew; 378 | self.y = yNew; 379 | atomize.atomically(function () { 380 | self.raw.x = xNew; 381 | self.raw.y = yNew; 382 | }, function () { 383 | self.blocked = false; 384 | }); 385 | } 386 | } 387 | this.bomberman.occupy(xCell, yCell, self, fun); 388 | } 389 | } else { 390 | atomize.atomically(function () { 391 | self.raw.x = xNew; 392 | self.raw.y = yNew; 393 | }, function () { 394 | self.x = xNew; 395 | self.y = yNew; 396 | self.blocked = false; 397 | }); 398 | } 399 | }, 400 | 401 | watch: function () { 402 | var self, fun; 403 | if (this.watching) { 404 | return; 405 | } 406 | this.watching = true; 407 | self = this; 408 | fun = function () { 409 | atomize.atomically(function () { 410 | if (self.x === self.raw.x && self.y === self.raw.y && self.dead === self.raw.dead) { 411 | atomize.retry(); 412 | } else { 413 | return {x: self.raw.x, y: self.raw.y, dead: self.raw.dead} 414 | } 415 | }, function (pos) { 416 | self.x = pos.x; 417 | self.y = pos.y 418 | self.dead = pos.dead; 419 | fun(); 420 | }); 421 | }; 422 | fun(); 423 | }, 424 | 425 | spawn: function () { 426 | var self, fun, x, y, directions, keys; 427 | self = this; 428 | x = Math.round((self.bomberman.width - 1) * Math.random()); 429 | y = Math.round((self.bomberman.height - 1) * Math.random()); 430 | fun = function (success) { 431 | if (success) { 432 | atomize.atomically(function () { 433 | self.raw.x = x + 0.5; 434 | self.raw.y = y + 0.5; 435 | self.raw.dead = false; 436 | }, function () { 437 | self.x = x + 0.5; 438 | self.y = y + 0.5; 439 | self.xCell = x; 440 | self.yCell = y; 441 | self.ready = true; 442 | self.dead = false; 443 | directions = {north: function () { self.north() }, 444 | south: function () { self.south() }, 445 | east: function () { self.east() }, 446 | west: function () { self.west() }}; 447 | if (self.bomberman.grid[x-1][y].wall) { 448 | delete directions.west; 449 | } 450 | if (self.bomberman.grid[x+1][y].wall) { 451 | delete directions.east; 452 | } 453 | if (self.bomberman.grid[x][y-1].wall) { 454 | delete directions.north; 455 | } 456 | if (self.bomberman.grid[x][y+1].wall) { 457 | delete directions.south; 458 | } 459 | keys = Object.keys(directions); 460 | directions[keys[Math.round((keys.length - 1) * Math.random())]](); 461 | }); 462 | } else { 463 | self.spawn(); 464 | } 465 | }; 466 | self.bomberman.occupy(x, y, self, fun); 467 | } 468 | }; 469 | 470 | function Bomberman(raw) { 471 | this.raw = raw; 472 | this.grid = []; 473 | this.players = {}; 474 | this.bombs = {}; 475 | } 476 | 477 | Bomberman.prototype = { 478 | width: 25, 479 | height: 25, 480 | 481 | dropBomb: function (x, y, bomb, cont) { 482 | var self, fun; 483 | self = this; 484 | fun = function (success) { 485 | if (success) { 486 | atomize.atomically(function () { 487 | self.raw.bombs.eventCount += 1; 488 | self.raw.bombs.bombs[self.raw.bombs.eventCount] = bomb.raw; 489 | bomb.raw.id = self.raw.bombs.eventCount; 490 | return true; 491 | }, cont); 492 | } else { 493 | cont(false); 494 | } 495 | }; 496 | self.grid[x][y].placeBomb(bomb, fun); 497 | }, 498 | 499 | deleteBomb: function (bomb) { 500 | var self = this; 501 | atomize.atomically(function () { 502 | if (bomb.raw === self.raw.bombs.bombs[bomb.id]) { 503 | self.raw.bombs.eventCount += 1; 504 | delete self.raw.bombs.bombs[bomb.id]; 505 | } 506 | }); 507 | }, 508 | 509 | occupy: function (x, y, player, cont) { 510 | this.grid[x][y].occupy(player, cont); 511 | }, 512 | 513 | unoccupy: function (x, y, player) { 514 | this.grid[x][y].unoccupy(player); 515 | }, 516 | 517 | watchGrid: function () { 518 | var x, y; 519 | for (x = 0; x < this.grid.length; x += 1) { 520 | for (y = 0; y < this.grid[x].length; y += 1) { 521 | this.grid[x][y].watch(); 522 | } 523 | } 524 | }, 525 | 526 | watchPlayers: function () { 527 | var fun, self, players, keys, i; 528 | self = this; 529 | fun = function (eventCount) { 530 | atomize.atomically( 531 | function () { 532 | if (self.raw.players.eventCount === eventCount) { 533 | atomize.retry(); 534 | } else { 535 | players = {}; 536 | keys = Object.keys(self.raw.players.players); 537 | for (i = 0; i < keys.length; i += 1) { 538 | players[keys[i]] = self.raw.players.players[keys[i]]; 539 | } 540 | return {players: players, eventCount: self.raw.players.eventCount}; 541 | } 542 | }, function (result) { 543 | self.players = {}; 544 | keys = Object.keys(result.players); 545 | for (i = 0; i < keys.length; i += 1) { 546 | if (result.players[keys[i]] === self.me.raw) { 547 | self.players[keys[i]] = self.me; 548 | } else { 549 | self.players[keys[i]] = new Player(self, result.players[keys[i]]); 550 | self.players[keys[i]].watch(); 551 | } 552 | } 553 | fun(result.eventCount); 554 | }); 555 | }; 556 | fun(0); 557 | }, 558 | 559 | watchBombs: function () { 560 | var fun, self, bombs, keys, i; 561 | self = this; 562 | fun = function (eventCount) { 563 | atomize.atomically(function () { 564 | if (self.raw.bombs.eventCount === eventCount) { 565 | atomize.retry(); 566 | } else { 567 | bombs = {}; 568 | keys = Object.keys(self.raw.bombs.bombs); 569 | for (i = 0; i < keys.length; i += 1) { 570 | bombs[keys[i]] = self.raw.bombs.bombs[keys[i]]; 571 | } 572 | return {bombs: bombs, eventCount: self.raw.bombs.eventCount}; 573 | } 574 | }, function (result) { 575 | self.bombs = {}; 576 | keys = Object.keys(result.bombs); 577 | for (i = 0; i < keys.length; i += 1) { 578 | self.bombs[keys[i]] = new Bomb(self, result.bombs[keys[i]]); 579 | self.bombs[keys[i]].maybeInit(); 580 | 581 | } 582 | fun(result.eventCount); 583 | }); 584 | }; 585 | fun(0); 586 | }, 587 | 588 | maybeInit: function () { 589 | var self, x, y, raw, cell; 590 | self = this; 591 | atomize.atomically( 592 | function () { 593 | if (undefined === self.raw.players) { 594 | self.raw.players = atomize.lift({eventCount: 0, players: {}}); 595 | } 596 | if (undefined === self.raw.bombs) { 597 | self.raw.bombs = atomize.lift({eventCount: 0, bombs: {}}); 598 | } 599 | if (undefined === self.raw.grid) { 600 | self.raw.grid = atomize.lift([]); 601 | for (x = 0; x < self.width; x += 1) { 602 | self.raw.grid[x] = atomize.lift([]); 603 | self.grid[x] = []; 604 | for (y = 0; y < self.height; y += 1) { 605 | raw = atomize.lift({}); 606 | self.raw.grid[x][y] = raw; 607 | cell = new Cell(self, x, y, raw); 608 | self.grid[x][y] = cell; 609 | } 610 | } 611 | } else { 612 | self.width = self.raw.grid.length; 613 | self.height = self.raw.grid[0].length; 614 | for (x = 0; x < self.width; x += 1) { 615 | self.grid[x] = []; 616 | for (y = 0; y < self.height; y += 1) { 617 | cell = new Cell(self, x, y, self.raw.grid[x][y]); 618 | self.grid[x][y] = cell; 619 | } 620 | } 621 | } 622 | return atomize.lift({type: "player", dead: false}); 623 | }, function (me) { 624 | self.me = new Player(self, me); 625 | self.watchGrid(); 626 | self.watchPlayers(); 627 | self.watchBombs(); 628 | atomize.atomically(function () { 629 | self.raw.players.eventCount += 1; 630 | self.raw.players.players[self.raw.players.eventCount] = me; 631 | self.me.raw.id = self.raw.players.eventCount; 632 | }, function () { 633 | self.me.spawn(); 634 | }); 635 | }); 636 | }, 637 | 638 | render: function (ctx) { 639 | var minDim, maxDim, wallLen, x, y, keys; 640 | minDim = Math.min(clientWidth, clientHeight); 641 | maxDim = Math.max(this.width, this.height); 642 | wallLen = minDim / maxDim; 643 | 644 | for (x = 0; x < this.grid.length; x += 1) { 645 | for (y = 0; y < this.grid[x].length; y += 1) { 646 | this.grid[x][y].render(ctx, wallLen); 647 | } 648 | } 649 | 650 | keys = Object.keys(this.players); 651 | for (x = 0; x < keys.length; x += 1) { 652 | this.players[keys[x]].render(ctx, wallLen); 653 | } 654 | 655 | keys = Object.keys(this.bombs); 656 | for (x = 0; x < keys.length; x += 1) { 657 | this.bombs[keys[x]].render(ctx, wallLen); 658 | } 659 | } 660 | }; 661 | 662 | function resizeCanvas() { 663 | var e; 664 | if (undefined !== canvas) { 665 | canvas.width = canvas.parentNode.offsetWidth; 666 | canvas.height = canvas.parentNode.offsetHeight; 667 | clientWidth = canvas.width; 668 | clientHeight = canvas.height; 669 | e = canvas.parentNode; 670 | while (undefined !== e && null !== e) { 671 | if (undefined !== e.clientHeight && undefined !== e.clientWidth && 672 | e.clientHeight > 0 && e.clientWidth > 0) { 673 | clientHeight = Math.min(clientHeight, e.clientHeight); 674 | clientWidth = Math.min(clientWidth, e.clientWidth); 675 | } 676 | e = e.parentNode; 677 | } 678 | canvasLeft = 10; 679 | canvasTop = 10; 680 | e = canvas.parentNode; 681 | while (undefined !== e && null !== e) { 682 | if (undefined !== e.offsetLeft && undefined !== e.offsetTop) { 683 | canvasLeft += e.offsetLeft; 684 | canvasTop += e.offsetTop; 685 | } 686 | e = e.parentNode; 687 | } 688 | } 689 | } 690 | 691 | function initCanvas() { 692 | resizeCanvas(); 693 | try { 694 | ctx = canvas.getContext("2d"); 695 | } catch (e) { 696 | } 697 | if (!ctx) { 698 | alert("Could not initialise 2D canvas. Change browser?"); 699 | } 700 | } 701 | 702 | function drawScene() { 703 | ctx.clearRect(0, 0, clientWidth, clientHeight); 704 | ctx.lineWidth = 1.0; 705 | ctx.lineCap = "round"; 706 | ctx.lineJoin = "round"; 707 | ctx.strokeStyle = "black"; 708 | bomberman.render(ctx); 709 | } 710 | 711 | requestAnimFrame = (function () { 712 | return (this.requestAnimationFrame || 713 | this.webkitRequestAnimationFrame || 714 | this.mozRequestAnimationFrame || 715 | this.oRequestAnimationFrame || 716 | this.msRequestAnimationFrame || 717 | function (/* function FrameRequestCallback */ callback, /* DOMElement Element */ element) { 718 | setTimeout(callback, 1000 / 60); 719 | }); 720 | })(); 721 | 722 | function tick () { 723 | if (undefined !== bomberman && undefined !== bomberman.me) { 724 | bomberman.me.step(); 725 | } 726 | drawScene(); 727 | requestAnimFrame(tick); 728 | } 729 | 730 | function doKeyDown (event) { 731 | switch (event.keyCode) { 732 | case 38: // Up 733 | bomberman.me.north(); 734 | break; 735 | case 40: // Down 736 | bomberman.me.south(); 737 | break; 738 | case 37: // Left 739 | bomberman.me.west(); 740 | break; 741 | case 39: // Right 742 | bomberman.me.east(); 743 | break; 744 | case 32: // Space 745 | bomberman.me.dropBomb(); 746 | break; 747 | } 748 | } 749 | 750 | function init () { 751 | atomize = new Atomize(); 752 | canvas = document.getElementById("game_canvas"); 753 | initCanvas(); 754 | atomize.onAuthenticated = function () { 755 | atomize.atomically( 756 | function () { 757 | if (undefined === atomize.root.bomberman) { 758 | atomize.root.bomberman = atomize.lift({}); 759 | } 760 | return atomize.root.bomberman; 761 | }, function (raw) { 762 | bomberman = new Bomberman(raw); 763 | bomberman.maybeInit(); 764 | requestAnimFrame(tick); 765 | window.addEventListener('keydown', doKeyDown, true); 766 | }); 767 | }; 768 | atomize.connect(); 769 | } 770 | -------------------------------------------------------------------------------- /bomberman/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | AtomizeJS: Bomberman! 5 | 6 | 7 | 8 | 9 | 23 | 24 | 25 |
26 | 27 |
28 | 29 | 30 | -------------------------------------------------------------------------------- /mongo-app.js: -------------------------------------------------------------------------------- 1 | /*global process, require */ 2 | /*jslint devel: true */ 3 | 4 | var http = require('http'); 5 | var cereal = require('cereal'); 6 | var atomize = require('atomize-server'); 7 | var mongodb = require('mongodb'); 8 | 9 | var Db = mongodb.Db, 10 | DbServer = mongodb.Server; 11 | 12 | var httpServer = http.createServer(); 13 | var port = 9999; 14 | var port_index = process.argv.indexOf('--port'); 15 | if (port_index > -1) { 16 | port = process.argv[port_index + 1]; 17 | } 18 | 19 | var atomizeServer = atomize.create(httpServer, '[/]atomize'); 20 | var atomizeClient = atomizeServer.client(); 21 | 22 | var dbName = 'test'; 23 | var dbCollections = {}; 24 | 25 | var db = new Db(dbName, 26 | new DbServer("127.0.0.1", 27017, {auto_reconnect: true, poolSize: 4}), 27 | {native_parser: false}); 28 | 29 | //atomizeClient.logging(true); 30 | 31 | function Item (collMongo, itemAtomize) { 32 | this.collMongo = collMongo; 33 | this.itemAtomize = itemAtomize; 34 | } 35 | Item.prototype = { 36 | constructor: Item, 37 | ignore: true, 38 | running: false, 39 | watchFun: function (inTxn, deltas) { 40 | var self = this, itemDelta, itemCopy; 41 | if (! this.ignore) { 42 | itemDelta = deltas.get(this.itemAtomize); 43 | if (!(itemDelta.added.length === 0 && 44 | itemDelta.deleted.length === 0 && 45 | itemDelta.modified.length === 1 && 46 | (itemDelta.modified[0] === '_storedVersion' || 47 | itemDelta.modified[0] === '_version'))) { 48 | if (inTxn) { 49 | this.itemAtomize._version += 1; 50 | } else { 51 | itemCopy = cereal.parse(cereal.stringify(this.itemAtomize)); 52 | itemCopy._storedVersion = itemCopy._version; 53 | this.collMongo.update({_id: itemCopy._id}, itemCopy, {safe:true}, function (err) { 54 | if (err) {throw err;} 55 | atomizeClient.atomically(function () { 56 | self.itemAtomize._storedVersion = itemCopy._storedVersion; 57 | }); 58 | }); 59 | } 60 | } 61 | } 62 | if (this.ignore && !inTxn) { 63 | this.ignore = false; 64 | } 65 | if (inTxn) { 66 | return deltas; 67 | } else { 68 | return true; 69 | } 70 | }, 71 | watch: function () { 72 | if (this.running) {return;} 73 | this.running = true; 74 | atomizeClient.watch(this.watchFun.bind(this), this.itemAtomize); 75 | } 76 | }; 77 | 78 | function Collection (collMongo, collAtomize) { 79 | this.collMongo = collMongo; 80 | this.collAtomize = collAtomize; 81 | this.items = {}; 82 | } 83 | 84 | Collection.prototype = { 85 | constructor: Collection, 86 | ignore: true, 87 | running: false, 88 | watchFun: function (inTxn, deltas) { 89 | var collDelta, idx, key; 90 | if (! this.ignore) { 91 | collDelta = deltas.get(this.collAtomize); 92 | if (inTxn) { 93 | while (collDelta.modified.length > 0) { 94 | key = collDelta.modified.pop(); 95 | collDelta.deleted.push(key); 96 | collDelta.added.push(key); 97 | } 98 | for (idx = 0; idx < collDelta.added.length; idx += 1) { 99 | key = collDelta.added[idx], 100 | this.addItem(key, this.collAtomize[key], true); 101 | } 102 | } else { 103 | while (collDelta.deleted.length > 0) { 104 | key = collDelta.deleted.pop(); 105 | this.items[key].running = false; 106 | delete this.items[key]; 107 | this.collMongo.remove({_name: key}); 108 | } 109 | while (collDelta.added.length > 0) { 110 | key = collDelta.added.pop(); 111 | this.addItem(key, this.collAtomize[key], false); 112 | } 113 | } 114 | } 115 | if (this.ignore && !inTxn) { 116 | this.ignore = false; 117 | } 118 | if (inTxn) { 119 | return deltas; 120 | } else { 121 | return this.running; 122 | } 123 | }, 124 | watch: function () { 125 | if (this.running) {return;} 126 | this.running = true; 127 | atomizeClient.watch(this.watchFun.bind(this), this.collAtomize); 128 | }, 129 | addItem: function (key, obj, inTxn) { 130 | if (inTxn) { 131 | obj._storedVersion = 0; 132 | obj._version = 1; 133 | obj._name = key; 134 | } else { 135 | var self = this; 136 | (function () { 137 | var objCopy = cereal.parse(cereal.stringify(obj)); 138 | objCopy._storedVersion = objCopy._version; 139 | self.collMongo.insert(objCopy, {safe: true}, function (err) { 140 | if (err) {throw err;} 141 | atomizeClient.atomically(function () { 142 | obj._storedVersion = objCopy._storedVersion; 143 | }, function () { 144 | self.items[key] = new Item(self.collMongo, obj); 145 | self.items[key].watch(); 146 | }); 147 | }); 148 | }()); 149 | } 150 | }, 151 | populateMongoAndWatch: function () { 152 | if (this.running) { return; } 153 | var self = this; 154 | this.ignore = true; 155 | atomizeClient.atomically(function () { 156 | var keys = Object.keys(self.collAtomize), key; 157 | while (keys.length > 0) { 158 | key = keys.pop(); 159 | self.addItem(key, self.collAtomize[key], true); 160 | } 161 | return cereal.parse(cereal.stringify(self.collAtomize)); 162 | }, function (c) { 163 | var keys = Object.keys(c), key; 164 | while (keys.length > 0) { 165 | key = keys.pop(); 166 | self.addItem(key, self.collAtomize[key], false); 167 | } 168 | self.watch(); 169 | }); 170 | }, 171 | populateAtomizeAndWatch: function () { 172 | if (this.running) { return; } 173 | var self = this; 174 | this.ignore = true; 175 | this.collMongo.find({}, function (err, cursor) { 176 | if (err) {throw err;} 177 | cursor.toArray(function (err, items) { 178 | if (err) {throw err;} 179 | atomizeClient.atomically(function () { 180 | var idx, item, itemCopy, itemAtomize, key; 181 | for (idx = 0; idx < items.length; idx += 1) { 182 | item = items[idx]; 183 | key = item._name; 184 | itemCopy = cereal.parse(cereal.stringify(item)); 185 | self.collAtomize[key] = atomizeClient.lift(itemCopy); 186 | } 187 | }, function () { 188 | for (idx = 0; idx < items.length; idx += 1) { 189 | item = items[idx]; 190 | key = item._name; 191 | self.items[key] = new Item(self.collMongo, self.collAtomize[key]); 192 | self.items[key].watch(); 193 | } 194 | self.watch(); 195 | }); 196 | }); 197 | }); 198 | } 199 | }; 200 | 201 | function Database (dbMongo, dbAtomize) { 202 | this.dbMongo = dbMongo; 203 | this.dbAtomize = dbAtomize; 204 | this.collections = {}; 205 | } 206 | 207 | Database.prototype = { 208 | constructor: Database, 209 | ignore: true, 210 | running: false, 211 | watchFun: function (inTxn, deltas) { 212 | var self = this, dbDelta, key, item; 213 | if (! this.ignore) { 214 | dbDelta = deltas.get(this.dbAtomize); 215 | if (! inTxn) { 216 | while (dbDelta.modified.length > 0) { 217 | key = dbDelta.modified.pop(); 218 | dbDelta.deleted.push(key); 219 | dbDelta.added.push(key); 220 | } 221 | while (dbDelta.deleted.length > 0) { 222 | key = dbDelta.modified.pop(); 223 | this.collections[key].running = false; 224 | delete this.collections[key]; 225 | this.dbMongo.dropCollection(key); 226 | } 227 | while (dbDelta.added.length > 0) { 228 | (function () { 229 | var key = dbDelta.added.pop(), 230 | collAtomize = self.dbAtomize[key]; 231 | self.dbMongo.createCollection( 232 | key, function (err, collMongo) { 233 | var c = new Collection(collMongo, collAtomize); 234 | self.collections[key] = c; 235 | c.populateMongoAndWatch(); 236 | }); 237 | }()); 238 | } 239 | } 240 | } 241 | if (this.ignore && !inTxn) { 242 | this.ignore = false; 243 | } 244 | if (inTxn) { 245 | return deltas; 246 | } else { 247 | return this.running; 248 | } 249 | }, 250 | watch: function () { 251 | if (this.running) {return;} 252 | this.running = true; 253 | atomizeClient.watch(this.watchFun.bind(this), this.dbAtomize); 254 | }, 255 | populateAtomizeAndWatch: function () { 256 | if (this.running) { return; } 257 | var self = this; 258 | this.ignore = true; 259 | this.dbMongo.collections(function (err, collections) { 260 | if (err) {throw err;} 261 | atomizeClient.atomically(function () { 262 | var collMongo, collAtomize, collsToStart = []; 263 | while (collections.length > 0) { 264 | collMongo = collections.pop(); 265 | if (/system\./.test(collMongo.collectionName)) { 266 | continue; 267 | } 268 | collAtomize = atomizeClient.lift({}); 269 | self.collections[collMongo.collectionName] = 270 | new Collection(collMongo, collAtomize); 271 | self.dbAtomize[collMongo.collectionName] = collAtomize; 272 | collsToStart.push(self.collections[collMongo.collectionName]); 273 | } 274 | return collsToStart; 275 | }, function (collsToStart) { 276 | var coll; 277 | while (collsToStart.length > 0) { 278 | coll = collsToStart.pop(); 279 | coll.populateAtomizeAndWatch(); 280 | } 281 | self.watch(); 282 | }); 283 | }); 284 | } 285 | }; 286 | 287 | db.open(function (err, dbMongo) { 288 | if (err) {throw(err);} 289 | 290 | atomizeClient.atomically(function () { 291 | var dbAtomize = atomizeClient.lift({}); 292 | atomizeClient.root.mongo = atomizeClient.lift({}); 293 | atomizeClient.root.mongo[dbName] = dbAtomize; 294 | return dbAtomize; 295 | }, function (dbAtomize) { 296 | var database = new Database(dbMongo, dbAtomize); 297 | database.populateAtomizeAndWatch(); 298 | console.log(" [*] Listening on 0.0.0.0:" + port); 299 | httpServer.listen(port, '0.0.0.0'); 300 | }); 301 | }); 302 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "AtomizeJS-Examples", 3 | "version": "0.0.0-unreleasable", 4 | "private": true, 5 | "dependencies": { 6 | "express" : ">=3.0.3", 7 | "atomize-server" : ">=0.4.15" 8 | }, 9 | "bin": { 10 | "atomize-examples-server": "./app.js" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /test/onewriter-compat.js: -------------------------------------------------------------------------------- 1 | // atomize-translate onewriter.js onewriter-compat.js atomize result console 2 | 3 | var obj, cont, writeCount = 9, expectedWrite, atomize; 4 | function writing () { 5 | atomize.atomically(function () { 6 | atomize.assign(obj, "x", atomize.access(obj, "x") + 1); 7 | if (atomize.access(obj, "x") !== expectedWrite) { 8 | throw "ItWentWrong"; 9 | } 10 | return atomize.access(obj, "x"); 11 | }, function (result) { 12 | expectedWrite += 1; 13 | writeCount -= 1; 14 | if (writeCount === 0) { 15 | console.log("Stopping writing at " + result); 16 | writeCount = 9; 17 | cont(false); 18 | } else { 19 | console.log("Wrote " + result); 20 | cont(true); 21 | } 22 | }); 23 | } 24 | function reading () { 25 | atomize.atomically(function () { 26 | if (0 === (atomize.access(obj, "x") % 10)) { 27 | atomize.assign(obj, "x", atomize.access(obj, "x") + 1); 28 | return {grabbed: true, value: atomize.access(obj, "x")}; 29 | } else { 30 | return {grabbed: false, value: atomize.access(obj, "x")}; 31 | } 32 | }, function (result) { 33 | if (result.grabbed === true) { 34 | expectedWrite = (atomize.access(obj, "x") + 1); 35 | console.log("Grabbed writer at " + result.value); 36 | } else { 37 | console.log("Read " + result.value); 38 | } 39 | cont(result.grabbed); 40 | }); 41 | } 42 | cont = function (writer) { 43 | if (writer) { 44 | setTimeout("writing();", 1000 / 60); 45 | } else { 46 | setTimeout("reading();", 1000 / 60); 47 | } 48 | }; 49 | function start () { 50 | atomize = new Atomize(); 51 | atomize.onAuthenticated = function () { 52 | atomize.atomically(function () { 53 | if ((undefined === atomize.access(atomize.root, "obj")) || (undefined === atomize.access(atomize.access(atomize.root, "obj"), "x"))) { 54 | atomize.assign(atomize.root, "obj", atomize.lift({x: 1})); 55 | return {writer: true, obj: atomize.access(atomize.root, "obj")}; 56 | } else { 57 | return {writer: false, obj: atomize.access(atomize.root, "obj")}; 58 | } 59 | }, function (result) { 60 | obj = result.obj; 61 | if (result.writer) { 62 | expectedWrite = 2; 63 | } 64 | cont(result.writer); 65 | }); 66 | }; 67 | atomize.connect(); 68 | } 69 | -------------------------------------------------------------------------------- /test/onewriter.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | OneWriter 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /test/onewriter.js: -------------------------------------------------------------------------------- 1 | // atomize-translate onewriter.js onewriter-compat.js atomize result console 2 | 3 | var obj, 4 | cont, 5 | writeCount = 9, 6 | expectedWrite, 7 | atomize; 8 | 9 | function writing() { 10 | atomize.atomically( 11 | function () { 12 | obj.x += 1; 13 | if (obj.x !== expectedWrite) { 14 | throw "ItWentWrong"; 15 | } 16 | return obj.x; 17 | }, 18 | function (result) { 19 | expectedWrite += 1; 20 | writeCount -= 1; 21 | if (writeCount === 0) { 22 | console.log("Stopping writing at " + result); 23 | writeCount = 9; 24 | cont(false); 25 | } else { 26 | console.log("Wrote " + result); 27 | cont(true) 28 | } 29 | } 30 | ); 31 | } 32 | 33 | function reading() { 34 | atomize.atomically( 35 | function () { 36 | if (0 === obj.x % 10) { 37 | obj.x += 1; 38 | return {grabbed: true, value: obj.x}; 39 | } else { 40 | return {grabbed: false, value: obj.x}; 41 | } 42 | }, 43 | function (result) { 44 | if (result.grabbed === true) { 45 | expectedWrite = obj.x + 1; 46 | console.log("Grabbed writer at " + result.value); 47 | } else { 48 | console.log("Read " + result.value); 49 | } 50 | cont(result.grabbed); 51 | } 52 | ); 53 | } 54 | 55 | cont = function (writer) { 56 | if (writer) { 57 | setTimeout("writing();", 1000/60); 58 | } else { 59 | setTimeout("reading();", 1000/60); 60 | } 61 | }; 62 | 63 | function start() { 64 | atomize = new Atomize(); 65 | atomize.onAuthenticated = function () { 66 | atomize.atomically(function () { 67 | if (undefined === atomize.root.obj || 68 | undefined === atomize.root.obj.x) { 69 | atomize.root.obj = atomize.lift({x: 1}); 70 | return {writer: true, obj: atomize.root.obj}; 71 | } else { 72 | return {writer: false, obj: atomize.root.obj}; 73 | } 74 | }, function (result) { 75 | obj = result.obj; 76 | if (result.writer) { 77 | expectedWrite = 2; 78 | } 79 | cont(result.writer); 80 | }); 81 | }; 82 | atomize.connect(); 83 | } 84 | -------------------------------------------------------------------------------- /test/queue-compat.js: -------------------------------------------------------------------------------- 1 | // atomize-translate queue.js queue-compat.js atomize console 2 | 3 | var myPos, writer, next = 0, value, atomize; 4 | function enqueue (e, cont) { 5 | atomize.atomically(function () { 6 | var obj = atomize.lift({val: e}); 7 | if (atomize.has(atomize.root, "queue")) { 8 | atomize.assign(atomize.access(atomize.root, "queue"), "next", obj); 9 | } 10 | atomize.assign(atomize.root, "queue", obj); 11 | }, function (result) { 12 | cont(); 13 | }); 14 | } 15 | function dequeue (cont) { 16 | if (undefined === myPos) { 17 | atomize.atomically(function () { 18 | if (!atomize.has(atomize.root, "queue")) { 19 | atomize.retry(); 20 | } 21 | return [atomize.access(atomize.root, "queue"), atomize.access(atomize.access(atomize.root, "queue"), "val")]; 22 | }, function (elem) { 23 | myPos = atomize.access(elem, 0); 24 | cont(atomize.access(elem, 1)); 25 | }); 26 | } else { 27 | atomize.atomically(function () { 28 | if (!atomize.has(myPos, "next")) { 29 | atomize.retry(); 30 | } 31 | return [atomize.access(myPos, "next"), atomize.access(atomize.access(myPos, "next"), "val")]; 32 | }, function (elem) { 33 | myPos = atomize.access(elem, 0); 34 | cont(atomize.access(elem, 1)); 35 | }); 36 | } 37 | } 38 | function write () { 39 | enqueue(next, function (e) { 40 | next += 1; 41 | loop(); 42 | }); 43 | } 44 | function read () { 45 | dequeue(function (result) { 46 | value = result; 47 | loop(); 48 | }); 49 | } 50 | function loop () { 51 | if (writer) { 52 | setTimeout("write();", 1); 53 | } else { 54 | setTimeout("read();", 1); 55 | } 56 | } 57 | function log () { 58 | if (writer) { 59 | console.log("Writing: " + next); 60 | } else { 61 | console.log("Dequeued: " + value); 62 | } 63 | debug(); 64 | } 65 | function debug () { 66 | setTimeout("log();", 1000); 67 | } 68 | function start () { 69 | atomize = new Atomize(); 70 | atomize.onAuthenticated = function () { 71 | atomize.atomically(function () { 72 | if (undefined === atomize.access(atomize.root, "queueWriter")) { 73 | atomize.assign(atomize.root, "queueWriter", "taken"); 74 | return true; 75 | } else { 76 | return false; 77 | } 78 | }, function (w) { 79 | writer = w; 80 | loop(); 81 | debug(); 82 | }); 83 | }; 84 | atomize.connect(); 85 | } 86 | -------------------------------------------------------------------------------- /test/queue.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Queue 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /test/queue.js: -------------------------------------------------------------------------------- 1 | // atomize-translate queue.js queue-compat.js atomize console 2 | 3 | /*global atomize */ 4 | /*jslint browser: true, devel: true */ 5 | 6 | var myPos, 7 | writer, 8 | next = 0, 9 | value, 10 | atomize; 11 | 12 | function enqueue(e, cont) { 13 | atomize.atomically(function () { 14 | var obj = atomize.lift({val: e}); 15 | if ('queue' in atomize.root) { 16 | atomize.root.queue.next = obj; 17 | } 18 | atomize.root.queue = obj; 19 | }, function (result) { 20 | cont(); 21 | }); 22 | } 23 | 24 | function dequeue(cont) { 25 | if (undefined === myPos) { 26 | atomize.atomically(function () { 27 | if (! ('queue' in atomize.root)) { 28 | atomize.retry(); 29 | } 30 | return [atomize.root.queue, atomize.root.queue.val]; 31 | }, function (elem) { 32 | myPos = elem[0]; 33 | cont(elem[1]); 34 | }); 35 | } else { 36 | atomize.atomically(function () { 37 | if (! ('next' in myPos)) { 38 | atomize.retry(); 39 | } 40 | return [myPos.next, myPos.next.val]; 41 | }, function (elem) { 42 | myPos = elem[0]; 43 | cont(elem[1]); 44 | }); 45 | } 46 | } 47 | 48 | function write() { 49 | enqueue(next, function (e) { next += 1; loop(); }); 50 | } 51 | 52 | function read() { 53 | dequeue(function (result) { value = result; loop(); }); 54 | } 55 | 56 | function loop() { 57 | if (writer) { 58 | setTimeout("write();", 1); 59 | } else { 60 | setTimeout("read();", 1); 61 | } 62 | } 63 | 64 | function log() { 65 | if (writer) { 66 | console.log("Writing: " + next); 67 | } else { 68 | console.log("Dequeued: " + value); 69 | } 70 | debug(); 71 | } 72 | 73 | function debug() { 74 | setTimeout("log();", 1000); 75 | } 76 | 77 | function start() { 78 | atomize = new Atomize(); 79 | atomize.onAuthenticated = function () { 80 | atomize.atomically(function () { 81 | if (undefined === atomize.root.queueWriter) { 82 | atomize.root.queueWriter = "taken"; 83 | return true; 84 | } else { 85 | return false; 86 | } 87 | }, function (w) { 88 | writer = w; 89 | loop(); 90 | debug(); 91 | }); 92 | }; 93 | atomize.connect(); 94 | } 95 | -------------------------------------------------------------------------------- /test/retry-compat.js: -------------------------------------------------------------------------------- 1 | // atomize-translate retry.js retry-compat.js atomize console 2 | var atomize; 3 | function start () { 4 | atomize = new Atomize(); 5 | 6 | atomize.onAuthenticated = function () { 7 | atomize.atomically(function () { 8 | if (undefined === atomize.access(atomize.root, "retryDecider")) { 9 | atomize.assign(atomize.root, "retryDecider", 1); 10 | return true; 11 | } else { 12 | atomize.assign(atomize.root, "retryDecider", atomize.access(atomize.root, "retryDecider") + 1); 13 | return false; 14 | } 15 | }, function (writer) { 16 | atomize.atomically(function () { 17 | if (writer) { 18 | if (1 === atomize.access(atomize.root, "retryDecider")) { 19 | atomize.retry(); 20 | } else { 21 | atomize.assign(atomize.root, "notified", atomize.lift(Date())); 22 | atomize.erase(atomize.root, "retryDecider"); 23 | return "notified others"; 24 | } 25 | } else { 26 | if (undefined === atomize.access(atomize.root, "notified")) { 27 | atomize.retry(); 28 | } else { 29 | return "" + atomize.access(atomize.root, "notified"); 30 | } 31 | } 32 | }, function (result) { 33 | console.log(result); 34 | }); 35 | }); 36 | }; 37 | atomize.connect(); 38 | } 39 | -------------------------------------------------------------------------------- /test/retry.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Retry 5 | 6 | 7 | 8 | 9 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /test/retry.js: -------------------------------------------------------------------------------- 1 | // atomize-translate retry.js retry-compat.js atomize console 2 | var atomize; 3 | function start() { 4 | atomize = new Atomize(); 5 | atomize.onAuthenticated = function () { 6 | atomize.atomically(function () { 7 | if (undefined === atomize.root.retryDecider) { 8 | atomize.root.retryDecider = 1; 9 | return true; 10 | } else { 11 | atomize.root.retryDecider += 1; 12 | return false 13 | } 14 | }, function (writer) { 15 | atomize.atomically(function () { 16 | if (writer) { 17 | if (1 === atomize.root.retryDecider) { 18 | atomize.retry(); 19 | } else { 20 | atomize.root.notified = atomize.lift(Date()); 21 | delete atomize.root.retryDecider 22 | return "notified others"; 23 | } 24 | } else { 25 | if (undefined === atomize.root.notified) { 26 | atomize.retry(); 27 | } else { 28 | return "" + atomize.root.notified; 29 | } 30 | } 31 | }, function (result) { 32 | console.log(result) 33 | }); 34 | }); 35 | }; 36 | atomize.connect(); 37 | } 38 | -------------------------------------------------------------------------------- /test/unittests.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | AtomizeJS Unit tests 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 |

AtomizeJS Unit tests

19 |

20 |
21 |

22 |
    23 |
    test markup, will be hidden
    24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /test/unittests.js: -------------------------------------------------------------------------------- 1 | // atomize-translate unittests.js unittests-compat.js atomize '$(document)' NiceException Error Semaphore 2 | 3 | function NiceException() {}; 4 | NiceException.prototype = Error.prototype; 5 | 6 | var niceException = new NiceException(); 7 | 8 | function withAtomize (clientsAry, test) { 9 | var atomize = new Atomize(); 10 | atomize.onAuthenticated = function () { 11 | atomize.atomically(function () { 12 | var key = Date(); 13 | atomize.root[key] = atomize.lift({}); 14 | return key; 15 | }, function (key) { 16 | var i; 17 | for (i = 0; i < clientsAry.length; i += 1) { 18 | clientsAry[i] = new Atomize(); 19 | clientsAry[i].setLogPrefix("(c" + i + "):"); 20 | if (0 === i) { 21 | clientsAry[i].onAuthenticated = function () { 22 | test(key, clientsAry, function () { 23 | for (i = 0; i < clientsAry.length; i += 1) { 24 | clientsAry[i].close(); 25 | clientsAry[i] = undefined; 26 | } 27 | atomize.atomically(function () { 28 | delete atomize.root[key]; 29 | }, function () { 30 | atomize.close(); 31 | }); 32 | }); 33 | }; 34 | } else { 35 | (function () { 36 | var j = i - 1; 37 | clientsAry[i].onAuthenticated = function () { 38 | clientsAry[j].connect(); 39 | } 40 | })(); 41 | } 42 | } 43 | clientsAry[clientsAry.length - 1].connect(); 44 | }); 45 | }; 46 | atomize.connect(); 47 | } 48 | 49 | function clients (n) { 50 | var ary = []; 51 | ary.length = n; 52 | return ary; 53 | } 54 | 55 | function contAndStart (cont) { 56 | cont(); 57 | start(); 58 | } 59 | 60 | function Semaphore (cont) { 61 | this.count = 0; 62 | this.cont = cont; 63 | } 64 | Semaphore.prototype = { 65 | fired: false, 66 | up: function () { 67 | if (this.fired) { 68 | throw "Semaphore Already Fired"; 69 | } 70 | this.count += 1; 71 | }, 72 | down: function () { 73 | if (this.fired) { 74 | throw "Semaphore Already Fired"; 75 | } 76 | this.count -= 1; 77 | if (0 === this.count) { 78 | this.fired = true; 79 | this.cont() 80 | } 81 | } 82 | }; 83 | 84 | $(document).ready(function(){ 85 | 86 | asyncTest("Empty transaction", 2, function () { 87 | withAtomize(clients(1), function (key, clients, cont) { 88 | var c1 = clients[0]; 89 | c1.atomically(function () { 90 | ok(true, "This txn has no read or writes so should run once"); 91 | }, function () { 92 | ok(true, "The continuation should be run"); 93 | contAndStart(cont); 94 | }); 95 | }); 96 | }); 97 | 98 | asyncTest("Await private empty root", 1, function () { 99 | withAtomize(clients(1), function (key, clients, cont) { 100 | var c1 = clients[0]; 101 | c1.atomically(function () { 102 | if (undefined === c1.root[key]) { 103 | c1.retry(); 104 | } 105 | return Object.keys(c1.root[key]).length; 106 | }, function (fieldCount) { 107 | strictEqual(fieldCount, 0, "Root object should be empty"); 108 | contAndStart(cont); 109 | }); 110 | }); 111 | }); 112 | 113 | asyncTest("Set Primitive", 1, function () { 114 | withAtomize(clients(1), function (key, clients, cont) { 115 | var c1 = clients[0], 116 | value = 5; 117 | c1.atomically(function () { 118 | if (undefined === c1.root[key]) { 119 | c1.retry(); 120 | } else if (undefined !== c1.root[key].field) { 121 | throw "Found existing field!"; 122 | } 123 | c1.root[key].field = value; 124 | return c1.root[key].field; 125 | }, function (result) { 126 | strictEqual(result, value, "Should have got back value"); 127 | contAndStart(cont); 128 | }); 129 | }); 130 | }); 131 | 132 | asyncTest("Set Empty Object", 1, function () { 133 | withAtomize(clients(1), function (key, clients, cont) { 134 | var c1 = clients[0], 135 | value = {}; 136 | c1.atomically(function () { 137 | if (undefined === c1.root[key]) { 138 | c1.retry(); 139 | } else if (undefined !== c1.root[key].field) { 140 | throw "Found existing field!"; 141 | } 142 | c1.root[key].field = c1.lift(value); 143 | return c1.root[key].field; 144 | }, function (result) { 145 | deepEqual(result, value, "Should have got back value"); 146 | contAndStart(cont); 147 | }); 148 | }); 149 | }); 150 | 151 | asyncTest("Set Complex Object", 1, function () { 152 | withAtomize(clients(1), function (key, clients, cont) { 153 | var c1 = clients[0], 154 | value = {a: "hello", b: true, c: 5, d: {}}; 155 | value.e = value; // add loop 156 | value.f = value.d; // add non-loop alias 157 | c1.atomically(function () { 158 | if (undefined === c1.root[key]) { 159 | c1.retry(); 160 | } else if (undefined !== c1.root[key].field) { 161 | throw "Found existing field!"; 162 | } 163 | c1.root[key].field = c1.lift(value); 164 | return c1.root[key].field; 165 | }, function (result) { 166 | deepEqual(result, value, "Should have got back value"); 167 | contAndStart(cont); 168 | }); 169 | }); 170 | }); 171 | 172 | asyncTest("Trigger (add field)", 1, function () { 173 | withAtomize(clients(2), function (key, clients, cont) { 174 | var c1 = clients[0], 175 | c2 = clients[1], 176 | trigger = "pop!"; 177 | 178 | c1.atomically(function () { 179 | if (undefined === c1.root[key] || 180 | undefined === c1.root[key].trigger) { 181 | c1.retry(); 182 | } 183 | return c1.root[key].trigger; 184 | }, function (result) { 185 | strictEqual(trigger, result, "Should have received the trigger"); 186 | contAndStart(cont); 187 | }); 188 | 189 | c2.atomically(function () { 190 | if (undefined === c2.root[key]) { 191 | c2.retry(); 192 | } 193 | if (undefined === c2.root[key].trigger) { 194 | c2.root[key].trigger = trigger; 195 | } else { 196 | throw "Found existing trigger!"; 197 | } 198 | }); // no need for a continuation here 199 | }); 200 | }); 201 | 202 | asyncTest("Trigger (add and change field)", 3, function () { 203 | withAtomize(clients(2), function (key, clients, cont) { 204 | var c1 = clients[0], 205 | c2 = clients[1], 206 | trigger1 = "pop!", 207 | trigger2 = undefined, 208 | sem = new Semaphore(function () {contAndStart(cont);}); 209 | sem.up(); sem.up(); 210 | c1.atomically(function () { 211 | if (undefined === c1.root[key] || 212 | undefined === c1.root[key].trigger) { 213 | c1.retry(); 214 | } 215 | if (c1.root[key].trigger == trigger1) { 216 | c1.root[key].trigger = trigger2; 217 | return true; 218 | } else { 219 | return false; 220 | } 221 | }, function (success) { 222 | ok(success, "Reached 1"); 223 | sem.down(); 224 | }); 225 | 226 | c2.atomically(function () { 227 | if (undefined === c2.root[key]) { 228 | c2.retry(); 229 | } 230 | if (undefined === c2.root[key].trigger) { 231 | c2.root[key].trigger = trigger1; 232 | } else { 233 | throw "Found existing trigger!"; 234 | } 235 | }, function () { 236 | ok(true, "Reached 2"); 237 | c2.atomically(function () { 238 | if (trigger2 != c2.root[key].trigger) { 239 | c2.retry(); 240 | } 241 | }, function () { 242 | ok(true, "Reached 3"); 243 | sem.down(); 244 | }); 245 | }); 246 | }); 247 | }); 248 | 249 | asyncTest("Trigger (add and remove field)", 3, function () { 250 | withAtomize(clients(2), function (key, clients, cont) { 251 | var c1 = clients[0], 252 | c2 = clients[1], 253 | trigger = "pop!", 254 | sem = new Semaphore(function () {contAndStart(cont);}); 255 | sem.up(); sem.up(); 256 | c1.atomically(function () { 257 | if (undefined === c1.root[key] || 258 | undefined === c1.root[key].trigger) { 259 | c1.retry(); 260 | } 261 | delete c1.root[key].trigger; 262 | }, function () { 263 | ok(true, "Reached 1"); 264 | sem.down(); 265 | }); 266 | 267 | c2.atomically(function () { 268 | if (undefined === c2.root[key]) { 269 | c2.retry(); 270 | } 271 | if (undefined === c2.root[key].trigger) { 272 | c2.root[key].trigger = trigger; 273 | } else { 274 | throw "Found existing trigger!"; 275 | } 276 | }, function () { 277 | ok(true, "Reached 2"); 278 | c2.atomically(function () { 279 | if (undefined !== c2.root[key].trigger) { 280 | c2.retry(); 281 | } 282 | }, function () { 283 | ok(true, "Reached 3"); 284 | sem.down(); 285 | }); 286 | }); 287 | }); 288 | }); 289 | 290 | asyncTest("Send Primitive", 1, function () { 291 | withAtomize(clients(2), function (key, clients, cont) { 292 | var c1 = clients[0], 293 | c2 = clients[1], 294 | value = 5; 295 | c1.atomically(function () { 296 | if (undefined === c1.root[key]) { 297 | c1.retry(); 298 | } else if (undefined !== c1.root[key].field) { 299 | throw "Found existing field!"; 300 | } 301 | c1.root[key].field = value; 302 | }); 303 | c2.atomically(function () { 304 | if (undefined === c2.root[key] || 305 | undefined === c2.root[key].field) { 306 | c2.retry(); 307 | } 308 | return c2.root[key].field; 309 | }, function (result) { 310 | strictEqual(result, value, "Should have got back value"); 311 | contAndStart(cont); 312 | }); 313 | }); 314 | }); 315 | 316 | asyncTest("Send Empty Object", 1, function () { 317 | withAtomize(clients(2), function (key, clients, cont) { 318 | var c1 = clients[0], 319 | c2 = clients[1], 320 | value = {}; 321 | c1.atomically(function () { 322 | if (undefined === c1.root[key]) { 323 | c1.retry(); 324 | } else if (undefined !== c1.root[key].field) { 325 | throw "Found existing field!"; 326 | } 327 | c1.root[key].field = c1.lift(value); 328 | }); 329 | c2.atomically(function () { 330 | if (undefined === c2.root[key] || 331 | undefined === c2.root[key].field) { 332 | c2.retry(); 333 | } 334 | // need to ensure we read deep 335 | Cereal.stringify(c2.root[key].field); 336 | return c2.root[key].field; 337 | }, function (result) { 338 | deepEqual(result, value, "Should have got back value"); 339 | contAndStart(cont); 340 | }); 341 | }); 342 | }); 343 | 344 | asyncTest("Send Complex Object", 1, function () { 345 | withAtomize(clients(2), function (key, clients, cont) { 346 | var c1 = clients[0], 347 | c2 = clients[1], 348 | value = {a: "hello", b: true, c: 5, d: {}}; 349 | value.e = value; // add loop 350 | value.f = value.d; // add non-loop alias 351 | c1.atomically(function () { 352 | if (undefined === c1.root[key]) { 353 | c1.retry(); 354 | } else if (undefined !== c1.root[key].field) { 355 | throw "Found existing field!"; 356 | } 357 | c1.root[key].field = c1.lift(value); 358 | }); 359 | c2.atomically(function () { 360 | if (undefined === c2.root[key] || 361 | undefined === c2.root[key].field) { 362 | c2.retry(); 363 | } 364 | // need to ensure we read deep 365 | Cereal.stringify(c2.root[key].field); 366 | return c2.root[key].field; 367 | }, function (result) { 368 | deepEqual(result, value, "Should have got back value"); 369 | contAndStart(cont); 370 | }); 371 | }); 372 | }); 373 | 374 | asyncTest("Send and manipulate Array", 3, function () { 375 | withAtomize(clients(3), function (key, clients, cont) { 376 | var c1 = clients[0], 377 | c2 = clients[1]; 378 | c3 = clients[2], 379 | sem = new Semaphore(function () {contAndStart(cont);}); 380 | sem.up();sem.up();sem.up(); 381 | c1.atomically(function () { 382 | if (undefined === c1.root[key]) { 383 | c1.retry(); 384 | } 385 | c1.root[key].ary = c1.lift(['a']); 386 | c1.root[key].ary.push('b'); 387 | c1.root[key].done = true; 388 | return c1.root[key].ary.length; 389 | }, function (len) { 390 | strictEqual(len, 2, "Array should have length 2"); 391 | sem.down(); 392 | }); 393 | c2.atomically(function () { 394 | if (undefined === c2.root[key] || 395 | ! ('done' in c2.root[key])) { 396 | c2.retry(); 397 | } 398 | delete c2.root[key].done; 399 | return c2.root[key].ary.shift(); 400 | }, function (value) { 401 | strictEqual(value, 'a', "Should have shifted out value 'a'"); 402 | sem.down(); 403 | }); 404 | c3.atomically(function () { 405 | if (undefined === c3.root[key] || 406 | (! ('ary' in c3.root[key])) || 407 | c3.root[key].ary.length > 1) { 408 | c3.retry(); 409 | } 410 | return Object.keys(c3.root[key].ary); 411 | }, function (keys) { 412 | deepEqual(keys, ['0'], "Array should only have key '0'"); 413 | sem.down(); 414 | }); 415 | }); 416 | }); 417 | 418 | // For some reason, the current Proxy thing suggests all 419 | // descriptors should be configurable. Thus we don't test for the 420 | // 'configurable' meta-property here. This issue should go away 421 | // once "direct proxies" arrive. However, our implementation of 422 | // defineProperty does respect the configurable property, and 423 | // correspondingly, we must set it true if we hope to be able to 424 | // modify it later on. 425 | asyncTest("Keys, Enumerate, etc", 16, function () { 426 | withAtomize(clients(2), function (key, clients, cont) { 427 | var c1 = clients[0], 428 | c2 = clients[1], 429 | descriptors = {a: {value: 1, 430 | writable: true, 431 | enumerable: true, 432 | configurable: true}, 433 | b: {value: 2, 434 | writable: false, 435 | enumerable: true, 436 | configurable: true}, 437 | c: {value: 3, 438 | writable: true, 439 | enumerable: false, 440 | configurable: true}, 441 | d: {value: 4, 442 | writable: false, 443 | enumerable: false, 444 | configurable: true}, 445 | e: {value: undefined, 446 | writable: true, 447 | enumerable: true, 448 | configurable: true}, 449 | f: {value: undefined, 450 | writable: false, 451 | enumerable: true, 452 | configurable: true}, 453 | g: {value: undefined, 454 | writable: true, 455 | enumerable: false, 456 | configurable: true}, 457 | h: {value: undefined, 458 | writable: false, 459 | enumerable: false, 460 | configurable: true}}, 461 | attemptSet = function (obj, field, success) { 462 | var old = obj[field]; 463 | obj[field] = 'foo'; 464 | if (success) { 465 | if ('foo' !== obj[field]) { 466 | throw "Excepted write on field " + field + " to work. It didn't."; 467 | } 468 | } else { 469 | if (old !== obj[field]) { 470 | throw "Excepted write on field " + field + " to fail. It didn't fail."; 471 | } 472 | } 473 | }, 474 | sem = new Semaphore(function () {contAndStart(cont);}); 475 | sem.up();sem.up(); 476 | c1.atomically(function () { 477 | if (undefined === c1.root[key]) { 478 | c1.retry(); 479 | } 480 | var keys = Object.keys(descriptors), 481 | x, field, descriptor; 482 | for (x = 0; x < keys.length; x += 1) { 483 | field = keys[x]; 484 | descriptor = descriptors[field]; 485 | Object.defineProperty(c1.root[key], field, descriptor); 486 | } 487 | c1.root[key].done = true; 488 | }, function () { 489 | c1.atomically(function () { 490 | if (c1.root[key].done) { 491 | c1.retry(); 492 | } 493 | return {a: Object.getOwnPropertyDescriptor(c1.root[key], 'a'), 494 | b: Object.getOwnPropertyDescriptor(c1.root[key], 'b')}; 495 | }, function (descs) { 496 | deepEqual(descs.a, {configurable: true, 497 | enumerable: true, 498 | writable: false, 499 | value: 'foo'}, 500 | "Descriptor of 'a' was not modified correctly"); 501 | deepEqual(descs.b, {configurable: true, 502 | enumerable: false, 503 | writable: true, 504 | value: 2}, 505 | "Descriptor of 'b' was not modified correctly"); 506 | sem.down(); 507 | }); 508 | }); 509 | c2.atomically(function () { 510 | if (undefined === c2.root[key] || 511 | undefined === c2.root[key].done) { 512 | c2.retry(); 513 | } 514 | delete c2.root[key].done; 515 | var keys = Object.keys(c2.root[key]), 516 | names = Object.getOwnPropertyNames(c2.root[key]), 517 | enumerable = [], 518 | descriptors = {}, 519 | field, x; 520 | for (field in c2.root[key]) { 521 | enumerable.push(field); 522 | } 523 | for (x = 0; x < names.length; x += 1) { 524 | field = names[x]; 525 | descriptors[field] = Object.getOwnPropertyDescriptor(c2.root[key], field); 526 | } 527 | attemptSet(c2.root[key], 'a', true); 528 | attemptSet(c2.root[key], 'b', false); 529 | attemptSet(c2.root[key], 'c', true); 530 | attemptSet(c2.root[key], 'd', false); 531 | attemptSet(c2.root[key], 'e', true); 532 | attemptSet(c2.root[key], 'f', false); 533 | attemptSet(c2.root[key], 'g', true); 534 | attemptSet(c2.root[key], 'h', false); 535 | // just going to modify individual properties to check 536 | // they get communicated 537 | Object.defineProperty(c2.root[key], 'a', {writable: false}); 538 | Object.defineProperty(c2.root[key], 'b', {enumerable: false}); 539 | // demonstrate that nested txns get merged correctly 540 | // with parent mods of descriptors 541 | c2.atomically(function () { 542 | Object.defineProperty(c2.root[key], 'b', {writable: true}); 543 | }); 544 | return {keys: keys.sort(), 545 | names: names.sort(), 546 | enumerable: enumerable.sort(), 547 | descriptors: descriptors, 548 | hasA: 'a' in c2.root[key], 549 | hasC: 'c' in c2.root[key], 550 | hasE: 'e' in c2.root[key], 551 | hasG: 'g' in c2.root[key], 552 | hasZ: 'z' in c2.root[key], 553 | hasOwnA: ({}).hasOwnProperty.call(c2.root[key], 'a'), 554 | hasOwnC: ({}).hasOwnProperty.call(c2.root[key], 'c'), 555 | hasOwnE: ({}).hasOwnProperty.call(c2.root[key], 'e'), 556 | hasOwnG: ({}).hasOwnProperty.call(c2.root[key], 'g'), 557 | hasOwnZ: ({}).hasOwnProperty.call(c2.root[key], 'z')}; 558 | }, function (result) { 559 | deepEqual(result.keys, ['a', 'b', 'e', 'f'], 560 | "Keys should have found enumerable fields"); 561 | deepEqual(result.enumerable, ['a', 'b', 'e', 'f'], 562 | "Enumeration should have found enumerable fields"); 563 | deepEqual(result.names, ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'], 564 | "Should have found field names 'a' to 'h'"); 565 | deepEqual(result.descriptors, descriptors, 566 | "Should have got same descriptors back"); 567 | ok(result.hasA, "Should have found field 'a'"); 568 | ok(result.hasC, "Should have found field 'c'"); 569 | ok(result.hasE, "Should have found field 'e'"); 570 | ok(result.hasG, "Should have found field 'g'"); 571 | ok(! result.hasZ, "Should not have found field 'z'"); 572 | ok(result.hasOwnA, "Should have found own field 'a'"); 573 | ok(result.hasOwnC, "Should have found own field 'c'"); 574 | ok(result.hasOwnE, "Should have found own field 'e'"); 575 | ok(result.hasOwnG, "Should have found own field 'g'"); 576 | ok(! result.hasOwnZ, "Should not have found own field 'z'"); 577 | sem.down(); 578 | }); 579 | }); 580 | }); 581 | 582 | asyncTest("Triggers: Multiple concurrent retries, multiple clients", 6, function () { 583 | withAtomize(clients(2), function (key, clients, cont) { 584 | var c1 = clients[0], 585 | c2 = clients[1], 586 | sem = new Semaphore(function () {contAndStart(cont);}); 587 | sem.up();sem.up();sem.up();sem.up(); 588 | c1.atomically(function () { 589 | if (undefined === c1.root[key] || 590 | undefined === c1.root[key].ready) { 591 | c1.retry(); 592 | } 593 | c1.root[key].ready = ! c1.root[key].ready; // 2. Flip it false to true 594 | }, function () { 595 | ok(true, "Reached 1"); 596 | sem.down(); 597 | }); 598 | // We do the 'gone' thing because otherwise c1's txns can 599 | // create and remove it, before c2 spots its 600 | // existence. I.e. classic race condition. 601 | c1.atomically(function () { 602 | if (undefined === c1.root[key] || 603 | undefined === c1.root[key].ready || 604 | ! c1.root[key].ready) { 605 | c1.retry(); 606 | } 607 | delete c1.root[key].ready; // 3. Delete it 608 | c1.root[key].gone = true; 609 | }, function () { 610 | ok(true, "Reached 2"); 611 | sem.down(); 612 | }); 613 | c2.atomically(function () { 614 | if (undefined === c2.root[key] || 615 | (undefined === c2.root[key].ready && 616 | undefined === c2.root[key].gone)) { 617 | c2.retry(); // A. Await its existence 618 | } 619 | }, function () { 620 | ok(true, "Reached 3"); 621 | c2.atomically(function () { 622 | if (Object.hasOwnProperty.call(c2.root[key], 'ready')) { 623 | c2.retry(); // B. Await its disappearance 624 | } 625 | ok(c2.root[key].gone, "If 'ready' has gone, 'gone' must be truth"); 626 | delete c2.root[key].gone; 627 | }, function () { 628 | ok(true, "Reached 4"); 629 | sem.down(); // C. All done 630 | }); 631 | }); 632 | c2.atomically(function () { 633 | if (undefined === c2.root[key]) { 634 | c2.retry(); 635 | } 636 | c2.root[key].ready = false; // 1. Create it as false 637 | }, function () { 638 | ok(true, "Reached 5"); 639 | sem.down(); 640 | }); 641 | }); 642 | }); 643 | 644 | asyncTest("OrElse", 4, function () { 645 | withAtomize(clients(2), function (key, clients, cont) { 646 | var c1 = clients[0], 647 | c2 = clients[1], 648 | fun; 649 | fun = function (sum) { 650 | ok(true, "Reached 1"); // should reach this 4 times 651 | if (10 === sum) { // 10 === 1+2+3+4 652 | contAndStart(cont); 653 | return; 654 | } 655 | c1.orElse( 656 | [function () { 657 | if (undefined === c1.root[key] || 658 | undefined === c1.root[key].a) { 659 | c1.retry(); 660 | } 661 | c1.root[key].b = c1.root[key].a + 2; 662 | delete c1.root[key].a; 663 | return c1.root[key].b; 664 | }, function () { 665 | if (undefined === c1.root[key] || 666 | undefined === c1.root[key].b) { 667 | c1.retry(); 668 | } 669 | c1.root[key].c = c1.root[key].b + 3; 670 | delete c1.root[key].b; 671 | return c1.root[key].c; 672 | }, function () { 673 | if (undefined === c1.root[key] || 674 | undefined === c1.root[key].c) { 675 | c1.retry(); 676 | } 677 | c1.root[key].d = c1.root[key].c + 4; 678 | delete c1.root[key].c; 679 | return c1.root[key].d; 680 | }], fun); 681 | }; 682 | fun(0); 683 | c2.atomically(function () { 684 | if (undefined === c2.root[key]) { 685 | c2.retry(); 686 | } 687 | c2.root[key].a = 1; 688 | }); 689 | }); 690 | }); 691 | 692 | asyncTest("OrElse - observing order", 4, function () { 693 | // Same as before, but drop the deletes, and invert the order 694 | // of the orElse statements. As its deterministic choice, 695 | // should do the same as before. 696 | withAtomize(clients(2), function (key, clients, cont) { 697 | var c1 = clients[0], 698 | c2 = clients[1], 699 | fun; 700 | fun = function (sum) { 701 | ok(true, "Reached 1"); // should reach this 4 times 702 | if (10 === sum) { // 10 === 1+2+3+4 703 | contAndStart(cont); 704 | return; 705 | } 706 | c1.orElse( 707 | [function () { 708 | if (undefined === c1.root[key] || 709 | undefined === c1.root[key].c) { 710 | c1.retry(); 711 | } 712 | c1.root[key].d = c1.root[key].c + 4; 713 | return c1.root[key].d; 714 | }, function () { 715 | if (undefined === c1.root[key] || 716 | undefined === c1.root[key].b) { 717 | c1.retry(); 718 | } 719 | c1.root[key].c = c1.root[key].b + 3; 720 | return c1.root[key].c; 721 | }, function () { 722 | if (undefined === c1.root[key] || 723 | undefined === c1.root[key].a) { 724 | c1.retry(); 725 | } 726 | c1.root[key].b = c1.root[key].a + 2; 727 | return c1.root[key].b; 728 | }], fun); 729 | }; 730 | fun(0); 731 | c2.atomically(function () { 732 | if (undefined === c2.root[key]) { 733 | c2.retry(); 734 | } 735 | c2.root[key].a = 1; 736 | }); 737 | }); 738 | }); 739 | 740 | asyncTest("Nested retries", 3, function () { 741 | withAtomize(clients(2), function (key, clients, cont) { 742 | var c1 = clients[0], 743 | c2 = clients[1], 744 | sem = new Semaphore(function () {contAndStart(cont);}); 745 | sem.up();sem.up(); 746 | c1.atomically(function () { 747 | if (undefined === c1.root[key]) { 748 | c1.retry(); 749 | } 750 | c1.atomically(function () { 751 | if (undefined === c1.root[key].foo) { 752 | c1.retry(); // when this restarts, the outer thing will restart too 753 | } 754 | delete c1.root[key].foo.field; 755 | c1.root[key].bar = c1.lift({}); 756 | }, function () { 757 | // still in a txn here; and the previous txn 758 | // doesn't commit until our parent commits. 759 | }); 760 | }, function () { 761 | ok(true, "Reached 1"); 762 | c1.atomically(function () { 763 | if (undefined === c1.root[key].baz) { 764 | c1.retry(); 765 | } 766 | }, function () { 767 | sem.down(); 768 | }); 769 | }); 770 | c2.atomically(function () { 771 | if (undefined === c2.root[key]) { 772 | c2.retry(); 773 | } 774 | c2.root[key].foo = c2.lift({field: true}); 775 | }, function () { 776 | ok(true, "Reached 2"); 777 | c2.atomically(function () { 778 | if ('field' in c2.root[key].foo) { 779 | c2.retry(); 780 | } 781 | c2.root[key].baz = c2.lift({}); 782 | }, function () { 783 | ok(true, "Reached 3"); 784 | sem.down(); 785 | }); 786 | }); 787 | }); 788 | }); 789 | 790 | (function () { 791 | var clientCount = 6, 792 | clientConcurrency = 10, 793 | txnCount = 10; 794 | 795 | asyncTest("Rampaging Transactions 1 (this takes a while)", 796 | ((clientCount - 1) * clientConcurrency * txnCount) -1, function () { 797 | withAtomize(clients(clientCount), function (key, clients, cont) { 798 | var semaphore = new Semaphore(function () { contAndStart(cont); }), 799 | fun, x, y; 800 | fun = function (c) { 801 | c.atomically(function () { 802 | if (undefined === c.root[key] || 803 | undefined === c.root[key].obj) { 804 | c.retry(); 805 | } 806 | var keys = Object.keys(c.root[key].obj), 807 | max = 0, 808 | x, field, n, obj; 809 | for (x = 0; x < keys.length; x += 1) { 810 | field = parseInt(keys[x]); 811 | max = field > max ? field : max; 812 | if (undefined === n) { 813 | n = c.root[key].obj[field].num; 814 | if (0 === n) { 815 | return n; 816 | } 817 | } else if (n !== c.root[key].obj[field].num) { 818 | throw ("All fields should have the same number: " + 819 | n + " vs " + c.root[key].obj[field].num); 820 | } 821 | if (0.75 < Math.random()) { 822 | obj = c.lift({}); 823 | obj.num = n; 824 | c.root[key].obj[field] = obj; 825 | } 826 | c.root[key].obj[field].num -= 1; 827 | } 828 | n -= 1; 829 | max += 1; 830 | if (0.75 < Math.random()) { 831 | c.root[key].obj[max] = c.lift({num: n}); 832 | delete c.root[key].obj[keys[0]]; 833 | } 834 | return n; 835 | }, function (n) { 836 | if (n > 0) { 837 | ok(true, "Reached"); 838 | fun(c); 839 | } else { 840 | semaphore.down(); 841 | } 842 | }); 843 | }; 844 | // We use all but one client, and each of those gets 10 845 | // txns concurrently 846 | for (x = 1; x < clients.length; x += 1) { 847 | for (y = 0; y < clientConcurrency; y += 1) { 848 | semaphore.up(); 849 | fun(clients[x]); 850 | } 851 | } 852 | x = clients[0]; 853 | x.atomically(function () { 854 | if (undefined === x.root[key]) { 855 | x.retry(); 856 | } 857 | var obj = x.lift({}); 858 | for (y = 0; y < 5; y += 1) { 859 | obj[y] = x.lift({num: (clientCount - 1) * clientConcurrency * txnCount}); 860 | } 861 | x.root[key].obj = obj; 862 | }); 863 | }); 864 | }); 865 | }()); 866 | 867 | (function () { 868 | var clientCount = 6, 869 | clientConcurrency = 6, 870 | txnCount = 10; 871 | 872 | asyncTest("Rampaging Transactions 2 (this takes a while)", 873 | (clientCount - 1) * clientConcurrency * txnCount, function () { 874 | withAtomize(clients(clientCount), function (key, clients, cont) { 875 | var semaphore = new Semaphore(function () { contAndStart(cont); }), 876 | fun; 877 | fun = function (c, n) { 878 | c.atomically(function () { 879 | if (undefined === c.root[key] || 880 | undefined === c.root[key].obj) { 881 | c.retry(); 882 | } 883 | var ops, names, secret, x, name, op; 884 | 885 | // First verify the old thing 886 | ops = c.root[key].obj.log; 887 | if (undefined !== ops) { 888 | secret = ops.secret; 889 | names = Object.keys(ops); 890 | for (x = 0; x < names.length; x += 1) { 891 | name = names[x]; 892 | if ('secret' === name) { 893 | continue; 894 | } else if ('delete' === ops[name]) { 895 | if (({}).hasOwnProperty.call(c.root[key].obj, name)) { 896 | throw ("Found field which should be deleted: " + name) 897 | } 898 | } else if ('modify' === ops[name]) { 899 | if (! ({}).hasOwnProperty.call(c.root[key].obj, name)) { 900 | throw ("Failed to find field: " + name); 901 | } 902 | if (secret !== c.root[key].obj[name].modified.value) { 903 | throw ("Found the wrong modified value in field: " + name); 904 | } 905 | } else if ('create' === ops[name]) { 906 | if (! ({}).hasOwnProperty.call(c.root[key].obj, name)) { 907 | throw ("Failed to find field: " + name); 908 | } 909 | if (secret !== c.root[key].obj[name].created.value) { 910 | throw ("Found the wrong created value in field: " + name); 911 | } 912 | } else { 913 | throw ("Found unknown op: " + ops[name]); 914 | } 915 | } 916 | } 917 | 918 | secret = Math.random(); 919 | ops = {secret: secret}; 920 | for (x = 0; x < 20; x += 1) { 921 | name = Math.round(Math.random() * 50); 922 | op = Math.random(); 923 | if (op > 0.9) { 924 | delete c.root[key].obj[name]; 925 | ops[name] = 'delete'; 926 | } else if (op > 0.1 && ({}).hasOwnProperty.call(c.root[key].obj, name)) { 927 | c.root[key].obj[name].modified.value = secret; 928 | ops[name] = 'modify'; 929 | } else { 930 | c.root[key].obj[name] = c.lift({}); 931 | c.root[key].obj[name].created = c.lift({value: secret}); 932 | c.root[key].obj[name].modified = c.lift({value: secret}); 933 | ops[name] = 'create'; 934 | } 935 | } 936 | c.root[key].obj.log = c.lift(ops); 937 | }, function () { 938 | ok(true, "Reached"); 939 | n += 1; 940 | if (10 === n) { 941 | semaphore.down(); 942 | } else { 943 | fun(c, n); 944 | } 945 | }); 946 | }; 947 | // We use all but one client, and each of those gets 10 948 | // txns concurrently 949 | for (x = 1; x < clients.length; x += 1) { 950 | for (y = 0; y < clientConcurrency; y += 1) { 951 | semaphore.up(); 952 | fun(clients[x], 0); 953 | } 954 | } 955 | x = clients[0]; 956 | x.atomically(function () { 957 | if (undefined === x.root[key]) { 958 | x.retry(); 959 | } 960 | x.root[key].obj = x.lift({}); 961 | }); 962 | }); 963 | }); 964 | }()); 965 | 966 | }); 967 | --------------------------------------------------------------------------------