improve indexdb promised. fix multiple spaces access. add workspace persist + restore for mesh
This commit is contained in:
parent
a5cf4a7a87
commit
96145b370b
9 changed files with 192 additions and 64 deletions
|
|
@ -11,6 +11,10 @@
|
|||
|
||||
data.Index = IDBStore;
|
||||
|
||||
data.open = function() {
|
||||
return new IDBStore(...arguments);
|
||||
};
|
||||
|
||||
// https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API
|
||||
|
||||
let IDB, IRR, local = null;
|
||||
|
|
@ -35,6 +39,7 @@
|
|||
this.queue = [];
|
||||
this.idle = [];
|
||||
this.initCalled = false;
|
||||
this.created = {};
|
||||
}
|
||||
|
||||
IDBStore.delete = function(dbname) {
|
||||
|
|
@ -56,32 +61,60 @@
|
|||
return this;
|
||||
};
|
||||
|
||||
SP.promise = function() {
|
||||
let db = this;
|
||||
return {
|
||||
keys: (lower, upper) => new Promise(function(resolve, reject) {
|
||||
db.keys(resolve,lower,upper);
|
||||
}),
|
||||
|
||||
iterate: (lower, upper, nullterm) => new Promise(function(resolve, reject) {
|
||||
db.iterate(resolve, lower, upper, nullterm);
|
||||
SP.promise = function(store) {
|
||||
let db = this;
|
||||
let pc = {
|
||||
iterate: (opt = {}) => new Promise(function(resolve, reject) {
|
||||
let { lower, upper, collect } = opt;
|
||||
if (!collect) {
|
||||
let rval = opt.map ? {} : [];
|
||||
collect = (key, value) => {
|
||||
if (key !== null) {
|
||||
if (opt.map) {
|
||||
rval[key] = value;
|
||||
} else {
|
||||
rval.push({key, value});
|
||||
}
|
||||
} else {
|
||||
resolve(rval);
|
||||
}
|
||||
};
|
||||
} else {
|
||||
resolve();
|
||||
}
|
||||
db.iterate(collect, lower, upper, true, store);
|
||||
}),
|
||||
|
||||
get: (key) => new Promise(function(resolve, reject) {
|
||||
db.get(key, resolve);
|
||||
db.get(key, resolve, store);
|
||||
}),
|
||||
|
||||
put: (key, value) => new Promise(function(resolve, reject) {
|
||||
db.put(key, value, resolve);
|
||||
db.put(key, value, resolve, store);
|
||||
}),
|
||||
|
||||
remove: (key) => new Promise(function(resolve, reject) {
|
||||
db.remove(key, resolve);
|
||||
})
|
||||
db.remove(key, resolve, store);
|
||||
}),
|
||||
|
||||
// wait for all outstanding transactions to complete
|
||||
wait: () => new Promise(function(resolve, reject) {
|
||||
db.onIdle(promise);
|
||||
}),
|
||||
|
||||
use: (store) => {
|
||||
db.use(store);
|
||||
return pc;
|
||||
},
|
||||
};
|
||||
return pc;
|
||||
}
|
||||
|
||||
SP.keys = function(callback, lower, upper) {
|
||||
SP.keys = function(callback, lower, upper, store) {
|
||||
if (!this.db) {
|
||||
this.init();
|
||||
return this.queue.push(["keys", callback, lower, upper, store]);
|
||||
}
|
||||
let out = [];
|
||||
this.iterate(function(k,v) {
|
||||
if (k === null) {
|
||||
|
|
@ -89,25 +122,25 @@
|
|||
} else {
|
||||
out.push(k);
|
||||
}
|
||||
}, lower, upper, true);
|
||||
}, lower, upper, true, store);
|
||||
};
|
||||
|
||||
SP.iterate = function(callback, lower, upper, nullterm) {
|
||||
SP.iterate = function(callback, lower, upper, nullterm, store) {
|
||||
if (!this.db) {
|
||||
this.init();
|
||||
return this.queue.push(["iterate", callback, lower, upper]);
|
||||
return this.queue.push(["iterate", callback, lower, upper, nullterm, store]);
|
||||
}
|
||||
let range = lower && upper ? IRR.bound(lower,upper) :
|
||||
lower ? IRR.lowerBound(lower) :
|
||||
upper ? IRR.upperBound(upper) : undefined;
|
||||
// iterate over all db values for debugging
|
||||
this.db
|
||||
.transaction(this.name)
|
||||
.objectStore(this.current)
|
||||
.transaction(store)
|
||||
.objectStore(store)
|
||||
.openCursor(range).onsuccess = function(event) {
|
||||
let cursor = event.target.result;
|
||||
if (cursor) {
|
||||
callback(cursor.key,cursor.value);
|
||||
callback(cursor.key, cursor.value);
|
||||
cursor.continue();
|
||||
} else if (nullterm) {
|
||||
callback(null);
|
||||
|
|
@ -149,7 +182,7 @@
|
|||
}
|
||||
let db = storage.db = request.result;
|
||||
for (let store of storage.stores) {
|
||||
storage.store = db.createObjectStore(store);
|
||||
storage.created[store] = db.createObjectStore(store);
|
||||
storage.current = store;
|
||||
}
|
||||
event.target.transaction.oncomplete = function(event) {
|
||||
|
|
@ -181,12 +214,13 @@
|
|||
let i = 0, q = this.queue, e;
|
||||
while (i < q.length) {
|
||||
e = q[i++];
|
||||
switch (e[0]) {
|
||||
case 'iterate': this.iterate(e[1], e[2], e[3]); break;
|
||||
case 'put': this.put(e[1], e[2], e[3]); break;
|
||||
case 'get': this.get(e[1], e[2]); break;
|
||||
case 'remove': this.remove(e[1], e[2]); break;
|
||||
case 'clear': this.clear();
|
||||
switch (e.shift()) {
|
||||
case 'iterate': this.iterate(...e); break;
|
||||
case 'keys': this.keys(...e); break;
|
||||
case 'put': this.put(...e); break;
|
||||
case 'get': this.get(...e); break;
|
||||
case 'remove': this.remove(...e); break;
|
||||
case 'clear': this.clear(...e);
|
||||
}
|
||||
}
|
||||
this.queue = [];
|
||||
|
|
@ -196,7 +230,7 @@
|
|||
}
|
||||
};
|
||||
|
||||
SP.put = function(key, value, callback) {
|
||||
SP.put = function(key, value, callback, store = this.current) {
|
||||
if (local) {
|
||||
local[key] = value;
|
||||
if (callback) callback(true);
|
||||
|
|
@ -204,12 +238,12 @@
|
|||
}
|
||||
if (!this.db) {
|
||||
this.init();
|
||||
return this.queue.push(['put', key, value, callback]);
|
||||
return this.queue.push(['put', key, value, callback, store]);
|
||||
}
|
||||
try {
|
||||
let req = this.db
|
||||
.transaction(this.name, "readwrite")
|
||||
.objectStore(this.current).put(value, key);
|
||||
.transaction(store, "readwrite")
|
||||
.objectStore(store).put(value, key);
|
||||
if (callback) {
|
||||
req.onsuccess = function(event) { callback(true) };
|
||||
req.onerror = function(event) { callback(false) };
|
||||
|
|
@ -220,19 +254,19 @@
|
|||
}
|
||||
};
|
||||
|
||||
SP.get = function(key, callback) {
|
||||
SP.get = function(key, callback, store = this.current) {
|
||||
if (local) {
|
||||
if (callback) callback(local[key]);
|
||||
return;
|
||||
}
|
||||
if (!this.db) {
|
||||
this.init();
|
||||
return this.queue.push(['get', key, callback]);
|
||||
return this.queue.push(['get', key, callback, store]);
|
||||
}
|
||||
try {
|
||||
let req = this.db
|
||||
.transaction(this.name)
|
||||
.objectStore(this.current).get(key);
|
||||
.transaction(store)
|
||||
.objectStore(store).get(key);
|
||||
if (callback) {
|
||||
req.onsuccess = function(event) { callback(req.result) };
|
||||
req.onerror = function(event) { callback(null) };
|
||||
|
|
@ -243,7 +277,7 @@
|
|||
}
|
||||
};
|
||||
|
||||
SP.remove = function(key, callback) {
|
||||
SP.remove = function(key, callback, store = this.current) {
|
||||
if (local) {
|
||||
delete local[key];
|
||||
if (callback) callback(true);
|
||||
|
|
@ -251,12 +285,12 @@
|
|||
}
|
||||
if (!this.db) {
|
||||
this.init();
|
||||
return this.queue.push(['remove', key, callback]);
|
||||
return this.queue.push(['remove', key, callback, store]);
|
||||
}
|
||||
try {
|
||||
let req = this.db
|
||||
.transaction(this.name, "readwrite")
|
||||
.objectStore(this.current).delete(key);
|
||||
.transaction(store, "readwrite")
|
||||
.objectStore(store).delete(key);
|
||||
if (callback) {
|
||||
req.onsuccess = function(event) { callback(true) };
|
||||
req.onerror = function(event) { callback(false) };
|
||||
|
|
@ -267,14 +301,14 @@
|
|||
}
|
||||
};
|
||||
|
||||
SP.clear = function(key) {
|
||||
SP.clear = function(key, store = this.current) {
|
||||
if (!this.db) {
|
||||
this.init();
|
||||
return this.queue.push(['clear']);
|
||||
return this.queue.push(['clear', store]);
|
||||
}
|
||||
this.db
|
||||
.transaction(this.name, "readwrite")
|
||||
.objectStore(this.current).clear();
|
||||
.transaction(store, "readwrite")
|
||||
.objectStore(store).clear();
|
||||
};
|
||||
|
||||
})();
|
||||
|
|
|
|||
|
|
@ -8,7 +8,8 @@ let broker = gapp.broker;
|
|||
let call = broker.send;
|
||||
|
||||
function init() {
|
||||
let moto = self.moto,
|
||||
let stores = data.open('mesh', { stores:[ "admin", "cache", "space" ] }).init(),
|
||||
moto = self.moto,
|
||||
api = mesh.api,
|
||||
sky = false,
|
||||
dark = false,
|
||||
|
|
@ -16,7 +17,16 @@ function init() {
|
|||
zoomrev = true,
|
||||
zoomspd = 1,
|
||||
space = moto.Space,
|
||||
platform = space.platform;
|
||||
platform = space.platform,
|
||||
db = mesh.db = {
|
||||
admin: stores.promise('admin'),
|
||||
space: stores.promise('space'),
|
||||
cache: stores.promise('cache')
|
||||
};
|
||||
|
||||
// mark init time and use count
|
||||
db.admin.put("init", Date.now());
|
||||
db.admin.get("uses").then(v => db.admin.put("uses", (v||0) + 1));
|
||||
|
||||
// setup default workspace
|
||||
space.useDefaultKeys(false);
|
||||
|
|
@ -33,7 +43,9 @@ function init() {
|
|||
color: 0xdddddd,
|
||||
zoom: { reverse: true, speed: 1 },
|
||||
size: { width: 2000, depth: 2000, height: 1, maxz: 300 },
|
||||
grid: { major: 25, minor: 5, colorMajor: 0xcccccc, colorMinor: 0xeeeeee, colorX: 0xffaaaa, colorY: 0xaaaaff },
|
||||
grid: { major: 25, minor: 5,
|
||||
colorMajor: 0xcccccc, colorMinor: 0xeeeeee,
|
||||
colorX: 0xffaaaa, colorY: 0xaaaaff },
|
||||
});
|
||||
space.view.setZoom(zoomrev, zoomspd);
|
||||
|
||||
|
|
@ -46,10 +58,32 @@ function init() {
|
|||
// trigger ui building
|
||||
call.ui_build();
|
||||
|
||||
// reload stored space
|
||||
moto.client.on('ready', restore_space);
|
||||
|
||||
// hide loading curtain
|
||||
$d('curtain','none');
|
||||
}
|
||||
|
||||
function restore_space() {
|
||||
mesh.db.space.iterate({ map: true }).then(cached => {
|
||||
for (let [id, data] of Object.entries(cached)) {
|
||||
// restore group
|
||||
if (Array.isArray(data)) {
|
||||
let models = data.map(id => {
|
||||
let md = cached[id];
|
||||
return new mesh.model(md, id);
|
||||
});
|
||||
mesh.api.group.new(models, id)
|
||||
.centerModels()
|
||||
.centerXY()
|
||||
.floor()
|
||||
.focus();
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function ui_build() {
|
||||
// set app version
|
||||
$h('app-name', "Mesh:Tool");
|
||||
|
|
@ -190,6 +224,7 @@ function space_init(data) {
|
|||
case 'Backspace':
|
||||
case 'Delete':
|
||||
for (let s of selection.list()) {
|
||||
selection.remove(s);
|
||||
s.showBounds(false);
|
||||
s.remove();
|
||||
}
|
||||
|
|
@ -314,6 +349,7 @@ gapp.finalize("main.mesh", [
|
|||
"moto.client", // dep: moto.client
|
||||
"moto.broker", // dep: moto.broker
|
||||
"moto.space", // dep: moto.space
|
||||
"data.index", // dep: data.index
|
||||
"mesh.api", // dep: mesh.api
|
||||
"mesh.model", // dep: mesh.model
|
||||
"load.file", // dep: load.file
|
||||
|
|
|
|||
|
|
@ -191,8 +191,8 @@ let group = {
|
|||
},
|
||||
|
||||
// @param group {MeshModel[]}
|
||||
new(models) {
|
||||
return group.add(new mesh.group(models));
|
||||
new(models, id) {
|
||||
return group.add(new mesh.group(models, id));
|
||||
},
|
||||
|
||||
// @param group {MeshGroup}
|
||||
|
|
|
|||
|
|
@ -26,8 +26,8 @@ let worker = moto.client.fn;
|
|||
mesh.group = class MeshGroup extends mesh.object {
|
||||
|
||||
// @param group {MeshModel[]}
|
||||
constructor(models = []) {
|
||||
super();
|
||||
constructor(models = [], id) {
|
||||
super(id);
|
||||
this.group = new THREE.Group();
|
||||
this.models = [];
|
||||
for (let model of models) {
|
||||
|
|
@ -51,11 +51,14 @@ mesh.group = class MeshGroup extends mesh.object {
|
|||
space.update();
|
||||
call.model_add({model, group:this});
|
||||
worker.group_add({id: this.id, model:model.id});
|
||||
// update data store
|
||||
mesh.db.space.put(this.id, this.models.map(m => m.id));
|
||||
return this;
|
||||
}
|
||||
|
||||
// @param model {MeshModel}
|
||||
remove(model) {
|
||||
// console.log('group',this,'remove',model);
|
||||
// remove all models and group
|
||||
if (arguments.length === 0) {
|
||||
for (let m of this.models.slice()) {
|
||||
|
|
@ -63,18 +66,24 @@ mesh.group = class MeshGroup extends mesh.object {
|
|||
}
|
||||
return;
|
||||
}
|
||||
model.group = undefined;
|
||||
this.models.remove(model);
|
||||
this.group.remove(model.mesh);
|
||||
// create message for listeners
|
||||
call.model_remove({model, group:this});
|
||||
// trigger sync with worker
|
||||
worker.group_remove({id: this.id, model:model.id});
|
||||
worker.group_remove({id: this.id, model: model.id});
|
||||
// ensure worker cleanup of model
|
||||
model.remove(true);
|
||||
// auto-remove group when empty
|
||||
if (this.group.children.length === 0) {
|
||||
mesh.api.group.remove(this);
|
||||
// update worker state
|
||||
worker.object_destroy({id: this.id});
|
||||
// update object store
|
||||
mesh.db.space.remove(this.id);
|
||||
} else {
|
||||
// update data store
|
||||
mesh.db.space.put(this.id, this.models.map(m => m.id));
|
||||
}
|
||||
space.update();
|
||||
return this;
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ gapp.register("mesh.model", [
|
|||
let mesh = self.mesh = self.mesh || {};
|
||||
if (mesh.model) return;
|
||||
|
||||
let mapp = mesh;
|
||||
let space = moto.Space;
|
||||
let worker = moto.client.fn;
|
||||
|
||||
|
|
@ -41,8 +42,8 @@ let materials = mesh.material = {
|
|||
|
||||
/** 3D model rendered on plaform **/
|
||||
mesh.model = class MeshModel extends mesh.object {
|
||||
constructor(data) {
|
||||
super();
|
||||
constructor(data, id) {
|
||||
super(id);
|
||||
let { file, mesh } = data;
|
||||
|
||||
if (!mesh) {
|
||||
|
|
@ -57,6 +58,9 @@ mesh.model = class MeshModel extends mesh.object {
|
|||
|
||||
this.file = file || 'unnamed';
|
||||
this.mesh = this.load(mesh);
|
||||
|
||||
// persist in db so it can be restored on page load
|
||||
mapp.db.space.put(this.id, { file, mesh });
|
||||
}
|
||||
|
||||
type() {
|
||||
|
|
@ -179,9 +183,16 @@ mesh.model = class MeshModel extends mesh.object {
|
|||
remove() {
|
||||
if (arguments.length === 0) {
|
||||
// direct call requires pass through group
|
||||
this.group.remove(this);
|
||||
this.group = undefined;
|
||||
this.removed = 'pending';
|
||||
} else {
|
||||
// called from group
|
||||
// update worker state
|
||||
worker.object_destroy({id: this.id});
|
||||
// update object store
|
||||
mesh.db.space.remove(this.id);
|
||||
// tag removed for debugging
|
||||
this.removed = 'complete';
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -18,8 +18,8 @@ let worker = moto.client.fn;
|
|||
|
||||
mesh.object = class MeshObject {
|
||||
|
||||
constructor() {
|
||||
this.id = mesh.util.uuid();
|
||||
constructor(id) {
|
||||
this.id = id || mesh.util.uuid();
|
||||
worker.object_create({id: this.id, type: this.type()});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -27,11 +27,6 @@ function cacheUpdate(id, data) {
|
|||
Object.assign(cache[id], data);
|
||||
}
|
||||
|
||||
worker.bind("debug", (data, send) => {
|
||||
console.log({work_cache: cache});
|
||||
send.done();
|
||||
});
|
||||
|
||||
let model = {
|
||||
load(data) {
|
||||
let { vertices, name, id } = data;
|
||||
|
|
@ -93,10 +88,16 @@ let object = {
|
|||
}
|
||||
};
|
||||
|
||||
worker.bind("debug", (data, send) => {
|
||||
console.log({work_cache: cache});
|
||||
});
|
||||
|
||||
worker.bindObject({
|
||||
model,
|
||||
group,
|
||||
object
|
||||
});
|
||||
|
||||
worker.ready();
|
||||
|
||||
})();
|
||||
|
|
|
|||
|
|
@ -11,29 +11,51 @@ let moto = self.moto = self.moto || {},
|
|||
workurl = null, // url to load worker
|
||||
worknum = 1, // number of workers to start
|
||||
workers = [], // array of all workers
|
||||
queue = []; // array of work requests
|
||||
queue = [], // array of work requests
|
||||
topics = {}; // topic handler (broker lite for worker > client msgs)
|
||||
|
||||
if (moto.client) return;
|
||||
|
||||
gapp.register('moto.client');
|
||||
gapp.register('moto.client', [
|
||||
"add.array", // dep: add.array
|
||||
]);
|
||||
|
||||
function dispatch(topic, message) {
|
||||
for (let fn of topics[topic] || []) {
|
||||
fn(message);
|
||||
}
|
||||
}
|
||||
|
||||
// code is running in the browser / client context
|
||||
let client = moto.client = {
|
||||
|
||||
/** @returns [integer] total number of workers **/
|
||||
live: () => {
|
||||
return workers.length;
|
||||
},
|
||||
|
||||
/** @returns [integer] number of idle workers **/
|
||||
free: () => {
|
||||
return workers.filter(w => w.run === null).length;
|
||||
},
|
||||
|
||||
/** @returns [integer] max cpu cores available **/
|
||||
max: () => {
|
||||
return ccmax;
|
||||
},
|
||||
|
||||
/** process worker topic messages like "ready" **/
|
||||
on: (topic, fn) => {
|
||||
(topics[topic] = topics[topic] || []).addOnce(fn);
|
||||
},
|
||||
|
||||
/**
|
||||
* @returns {Object} registered worker function map
|
||||
* auto-filled when worker process uses bind() or bindObject()
|
||||
**/
|
||||
fn: {},
|
||||
|
||||
/** binds a function endpoint to a task name **/
|
||||
bind: (task) => {
|
||||
if (!client.fn[task]) {
|
||||
client.fn[task] = (data, options) => {
|
||||
|
|
@ -42,6 +64,7 @@ let client = moto.client = {
|
|||
}
|
||||
},
|
||||
|
||||
/** add a promised task call to the work queue **/
|
||||
call: (task, data, options = {}) => {
|
||||
let { direct, stream } = options;
|
||||
return new Promise((resolve, reject) => {
|
||||
|
|
@ -62,11 +85,13 @@ let client = moto.client = {
|
|||
});
|
||||
},
|
||||
|
||||
/** danger: directly add an entry to the work queue **/
|
||||
queue: (task, data, options = {}) => {
|
||||
queue.push({task, data, options});
|
||||
client.kick();
|
||||
},
|
||||
|
||||
/** check work queue and distribute tasks to free workers **/
|
||||
kick: () => {
|
||||
if (queue.length === 0) {
|
||||
return;
|
||||
|
|
@ -172,11 +197,19 @@ let client = moto.client = {
|
|||
worker.index = i;
|
||||
worker.onmessage = function(e) {
|
||||
let reply = e.data;
|
||||
|
||||
// handle special case for task binding
|
||||
if (reply.bind) {
|
||||
client.bind(reply.bind);
|
||||
return;
|
||||
}
|
||||
|
||||
// handle special case when worker ready
|
||||
if (reply.ready) {
|
||||
dispatch('ready', reply.ready);
|
||||
return;
|
||||
}
|
||||
|
||||
if (worker.run === null) {
|
||||
console.log({client_unhandled: e});
|
||||
throw "message on idle worker";
|
||||
|
|
|
|||
|
|
@ -34,6 +34,10 @@ const dispatch = moto.worker = {
|
|||
}
|
||||
},
|
||||
|
||||
ready() {
|
||||
dispatch.send({ready: true});
|
||||
},
|
||||
|
||||
send(msg) {
|
||||
self.postMessage(msg);
|
||||
},
|
||||
|
|
|
|||
Loading…
Reference in a new issue