Last active
November 18, 2020 03:56
-
-
Save wonglok/94273e3b6702ba6ad04bae6511936182 to your computer and use it in GitHub Desktop.
EffectNode.JS 1 file Tree Based Framework for threejs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* CopyRight © Wong Lok 2020, MIT Licensed */ | |
class Teller { | |
constructor () { | |
const eventMap = {} | |
const on = (name, fn) => { | |
if (!eventMap[name]) { | |
eventMap[name] = [] | |
} | |
eventMap[name].push(fn) | |
} | |
const emit = (name, data) => { | |
if (!eventMap[name]) { | |
return false | |
} | |
eventMap[name].forEach((fn) => fn(data)) | |
} | |
const off = (name, fn) => { | |
if (eventMap[name]) { | |
const index = eventMap[name].indexOf(fn) | |
if (index >= 0) { | |
eventMap[name].splice(index, 1) | |
} | |
} | |
} | |
const cancel = (name) => { | |
if (eventMap[name]) { | |
delete eventMap[name] | |
} | |
} | |
return { | |
on, | |
emit, | |
off, | |
cancel | |
} | |
} | |
} | |
class EN { | |
static tellKids (vm, ev, data) { | |
if (vm) { | |
vm.events.emit(ev, data) | |
let desc = vm.children | |
if (vm.children && desc.length > 0) { | |
desc.forEach((kid) => { | |
EN.tellKids(kid, ev, data) | |
}) | |
} | |
} | |
} | |
static getUndefined (vm, key) { | |
console.log('access undefinedd', vm, key) | |
} | |
static getParent (vm) { | |
return vm.parent | |
} | |
static genID () { | |
return '_' + Math.random().toString(36).substr(2, 9) | |
} | |
static lookupHolder (vm, key) { | |
if (EN.getParent(vm) && EN.getParent(vm)[key]) { | |
return EN.getParent(vm) | |
} else { | |
vm = EN.getParent(vm) | |
if (!vm) { | |
return EN.getUndefined(vm, key) | |
} | |
return EN.lookupHolder(vm, key) | |
} | |
} | |
static traverseParent (vm, key) { | |
if (vm[key]) { | |
return vm[key] | |
} else if (EN.getParent(vm) && EN.getParent(vm)[key]) { | |
return EN.getParent(vm)[key] | |
} else { | |
vm = EN.getParent(vm) | |
if (!vm) { | |
return EN.getUndefined(vm, key) | |
} | |
return EN.traverseParent(vm, key) | |
} | |
} | |
static makeNode (node) { | |
return new Proxy(node, { | |
get: (obj, key) => { | |
if (key === 'root') { | |
return EN.traverseParent(obj, 'root') | |
} | |
return EN.traverseParent(obj, key) | |
}, | |
set: (obj, key, val, receiver) => { | |
if (key === 'root') { | |
console.log('key is preserved for ease of access to root node', key) | |
return true | |
} | |
return Reflect.set(obj, key, val, receiver) | |
} | |
}) | |
} | |
} | |
class MiniEngine { | |
constructor ({ name }) { | |
this.name = name | |
this.debug = false | |
let isAborted = false | |
this.tasks = [] | |
this.resizeTasks = [] | |
this.cleanTasks = [] | |
this.onLoop = (fnc) => { | |
this.tasks.push(fnc) | |
} | |
this.onResize = (fnc) => { | |
fnc() | |
this.resizeTasks.push(fnc) | |
} | |
this.onClean = (func) => { | |
this.cleanTasks.push(func) | |
} | |
let intv = 0 | |
let internalResize = () => { | |
clearTimeout(intv) | |
intv = setTimeout(() => { | |
this.resizeTasks.forEach(e => e()) | |
}, 16.8888) | |
} | |
window.addEventListener('resize', () => { | |
internalResize() | |
}) | |
this.doCleanUp = () => { | |
isAborted = true | |
try { | |
this.cleanTasks.forEach(e => e()) | |
} catch (e) { | |
console.error(e) | |
} | |
} | |
let isPaused = false | |
this.toggle = () => { | |
isPaused = !isPaused | |
} | |
this.pause = () => { | |
isPaused = true | |
} | |
this.play = () => { | |
isPaused = false | |
} | |
this.doMyWork = () => { | |
if (isAborted) { | |
return { | |
name: this.name, | |
duration: 0 | |
} | |
} | |
if (isPaused) { | |
return { | |
name: this.name, | |
duration: 0 | |
} | |
} | |
let start = window.performance.now() | |
try { | |
this.tasks.forEach(e => e()) | |
} catch (e) { | |
console.error(e) | |
} | |
let end = window.performance.now() | |
let duration = end - start | |
return { | |
name: this.name, | |
duration | |
} | |
} | |
} | |
} | |
class EffectNode { | |
constructor ({ _id, name = '', type = 'EffectNode', root = false, parent = false, ownLoop = false, ...props } = {}) { | |
this.props = props | |
this.type = type | |
this.root = root || this | |
this.parent = parent || false | |
this._id = _id || EN.genID() | |
this.name = name || this.root === this ? 'Root Node' : 'Sub Node' | |
this.engine = new MiniEngine({ name: this.name }) | |
this.children = [] | |
this.events = new Teller() | |
let protectedProperties = [ | |
'root', | |
'onLoop', | |
'onResize', | |
'onCleanUp', | |
'_', | |
'on', | |
'off', | |
'cancel', | |
'emit' | |
] | |
this.ctx = new Proxy(this, { | |
get: (obj, key) => { | |
// fast access | |
if (key === 'root') { | |
return obj.root | |
} | |
if (key === 'onLoop') { | |
return this.engine.onLoop | |
} | |
if (key === 'onResize') { | |
return this.engine.onResize | |
} | |
if (key === 'onCleanUp') { | |
return this.engine.onCleanUp | |
} | |
if (key === '_') { | |
return this | |
} | |
if (key === 'on') { | |
return this.events.on | |
} | |
if (key === 'off') { | |
return this.events.off | |
} | |
if (key === 'cancel') { | |
return this.events.cancel | |
} | |
if (key === 'emit') { | |
return this.events.emit | |
} | |
if (key === 'clean') { | |
return () => { | |
let idx = this.root.instances.findIndex(e => e === this) | |
let me = this.root.instances[idx] | |
me.engine.doCleanUp() | |
this.root.instances.splice(idx, 1) | |
} | |
} | |
return EN.traverseParent(obj, key) | |
}, | |
set: (obj, key, val, receiver) => { | |
if (protectedProperties.includes(key)) { | |
console.warn('protected read only properites', key) | |
return true | |
} | |
return Reflect.set(obj, key, val, receiver) | |
} | |
}) | |
if (this.isRoot) { | |
this.instances = [] | |
} | |
if (this.parent) { | |
this.parent.children.push(this) | |
} | |
this.root.instances.push(this) | |
if (this.isRoot && !ownLoop) { | |
this.startAll() | |
} | |
return this.ctx | |
} | |
tellDown (ev, data) { | |
EN.tellKids(this, ev, data) | |
} | |
startAll () { | |
let rAF = () => { | |
this.rAFID = requestAnimationFrame(rAF) | |
this.processAllNodes() | |
} | |
this.rAFID = requestAnimationFrame(rAF) | |
} | |
processAllNodes () { | |
if (this.root.profile) { | |
let stats = { | |
total: 0, | |
nodes: 0, | |
profile: [] | |
} | |
this.root.instances.forEach(each => { | |
let res = each.engine.doMyWork() | |
stats.profile.push(res) | |
stats.total += res.duration || 0 | |
}) | |
stats.nodes = stats.profile.length | |
console.log(stats) | |
this.events.emit('profile', stats) | |
} else { | |
this.root.instances.forEach(each => { | |
each.engine.doMyWork() | |
}) | |
} | |
} | |
get isRoot () { | |
return this.root === this | |
} | |
get allNodes () { | |
return this.root.instances | |
} | |
node (props) { | |
let ctx = new EffectNode({ ...props, root: this.root, parent: this }) | |
return ctx | |
} | |
} | |
let root = new EffectNode({ name: 'Wong Lok' }) | |
root.happy = { | |
life: true | |
} | |
console.log(root) | |
let child = root.node({ type: 'Child' }) | |
child.happy = { | |
life: 'life is good' | |
} | |
// root.profile = true | |
let grandkid = child.node({ type: 'GrandChild' }) | |
console.log(child) | |
console.log(grandkid) | |
root.on('sing', (data) => { | |
console.log('chid: parise my lord', data) | |
}) | |
child.on('sing', (data) => { | |
console.log('chid: parise my lord', data) | |
}) | |
grandkid.on('sing', (data) => { | |
console.log('grandchild: thank you jesus', data) | |
}) | |
grandkid.tellDown('sing', { call: 1 }) | |
child.tellDown('sing', { call: 2 }) | |
root.tellDown('sing', { call: 3 }) | |
/* | |
Proxy {props: {…}, type: "EffectNode", root: EffectNode, parent: false, _id: "_2c9zbylgk", …} | |
Proxy {props: {…}, type: "Child", root: EffectNode, parent: Proxy, _id: "_897ven0v2", …} | |
Proxy {props: {…}, type: "GrandChild", root: EffectNode, parent: Proxy, _id: "_r921mf7rz", …} | |
grandchild: thank you jesus {call: 1} | |
chid: parise my lord {call: 2} | |
grandchild: thank you jesus {call: 2} | |
chid: parise my lord {call: 3} | |
chid: parise my lord {call: 3} | |
grandchild: thank you jesus {call: 3} | |
*/ |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment