Skip to content

Commit

Permalink
🥷 Advanced Service Model Architecture
Browse files Browse the repository at this point in the history
  • Loading branch information
catpea committed Dec 28, 2024
1 parent 74b1199 commit 9d60b4c
Show file tree
Hide file tree
Showing 5 changed files with 210 additions and 23 deletions.
14 changes: 14 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
MAWP
---


## Two Tier Architecture

### User Interface Rendering

1. Zooming User Interface & SVG Drawing Support
- dom-zoom custom element


### Data Model & Applicaion Control
1. Node is a Dom Element like node with enhanced functionality
8 changes: 3 additions & 5 deletions browser.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,13 @@
import Services from './src/Services.js';
import UI from './src/UI.js';

const services = new Services();
const ui = new UI(services);

await services.load();
await services.start();

import UI from './src/UI.js';
const ui = new UI(services);
await ui.start();

console.log(`Startup at ${new Date().toISOString()}`);

window.addEventListener('beforeunload', function(event) {
ui.stop();
services.stop();
Expand Down
2 changes: 1 addition & 1 deletion index.html
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@
"dom-zoom": "./modules/dom-zoom/DomZoom.js",

"xml-parser": "./modules/xml-parser/XmlParser.js",

"sys-signal": "./modules/sys-signal/SysSignal.js"

}
}
</script>
Expand Down
26 changes: 24 additions & 2 deletions server.js
100644 → 100755
Original file line number Diff line number Diff line change
@@ -1,9 +1,31 @@
#!/usr/bin/env node

import Services from './src/Services.js';
const services = new Services();
import fs from 'fs';
import Node from './src/Services.js';

const services = new Node('services');

const main = new Node('main');
main.onStart = async () => console.log('ASYNC START GRRR')
main.once('stop', ()=>console.log('Main scene node got stoppppp...'))

services.watch('create', '/services/main/*', (x)=>console.info('[CREATE] new node in main scene', x))

const uppercase = new Node('uppercase');
const tee = new Node('tee');

services.create(main);
services.create(uppercase);
services.create(tee);

const mainInput = new Node('mainInput');
main.create(mainInput)

await services.load();
await services.start();

setTimeout(() => { },3_000) // TEST CTRL-C

// Example of cleanup task
const cleanup = () => {
console.log('Cleaning up resources...');
Expand Down
183 changes: 168 additions & 15 deletions src/Services.js
Original file line number Diff line number Diff line change
@@ -1,26 +1,179 @@
import Signal from 'sys-signal';

// WARN: this may run under node!
export default class Services {
id;
parent;
children = [];

constructor(id) {
this.id = id;
}

// TREE BUILDING //

create(node){
node.parent = this;
this.children.push(node);
this.propagate('create', node);
}

delete(node){
const index = this.children.indexOf(node);
if (index > -1) {
this.children.splice(index, 1);
}
this.propagate('delete', node);
}

// GETTERS AS TREE UTILITIES //

get root() {
let node = this;
while (node.parent) {
node = node.parent;
}
return node;
}

get path() {
const path = [];
let node = this;
while (node) {
path.unshift(node);
node = node.parent;
}
return path;
}

get all() {
const nodes = [];
const stack = [this.root]; // Initialize stack with the root node

while (stack.length > 0) {
const node = stack.pop();
nodes.push(node);
// Add child nodes to the stack (reverse order to maintain left-to-right traversal)
for (let i = node.children.length - 1; i >= 0; i--) {
stack.push(node.children[i]);
}
}
return nodes;
}

// PROPAGATING EMITTER SYSTEM //

watchers = {};
match(pattern, event) {
const parts = pattern.split('*');
let index = 0;
for (const part of parts) {
if (part === '') continue;
index = event.indexOf(part, index);
if (index === -1) return false;
index += part.length;
}
return true;
}
watch(name, pattern, callback, phase=1) {
if (!this.watchers[phase]) this.watchers[phase] = {};
if (!this.watchers[phase][name]) this.watchers[phase][name] = {};
if (!this.watchers[phase][name][pattern]) this.watchers[phase][name][pattern] = [];
this.watchers[phase][name][pattern].push(callback);
return () => this.unwatch(name, pattern, callback, phase);
}
unwatch(name, pattern, callback, phase) {
const index = this.watchers[phase][name][pattern].indexOf(callback);
if (index > -1) {
this.watchers[phase][name][pattern].splice(index, 1);
}
if (this.watchers[phase][name][pattern].length == 0) delete this.watchers[phase][name][pattern];
}
propagate(name, target, ...args) {
const event = '/' + target.path.map(o => o.id).join('/');
const taillessPath = target.path.slice(0,-1)
const phasedStack = [taillessPath, [target, ...taillessPath.toReversed()]];
let phasesStopped = false;
for (let phase = 0; phase < phasedStack.length; phase++) {
if (phasesStopped) break;
let propagationStopped = false;
for (const currentTarget of phasedStack[phase]) {
if (propagationStopped) break;
if(currentTarget.watchers[phase]){
if(currentTarget.watchers[phase][name]){
for (const pattern in currentTarget.watchers[phase][name]) {
if (this.match(pattern, event)) {
let immediatePropagationStopped = false;
for (const callback of currentTarget.watchers[phase][name][pattern]) {
if (immediatePropagationStopped) break;
const packet = {
phase,
event,
target,
currentTarget,
stopPropagation: () => { phasesStopped = true; propagationStopped = true },
stopImmediatePropagation: () => { phasesStopped = true; propagationStopped = true; immediatePropagationStopped = true; },
};
callback.bind(currentTarget)(packet);
} // for [IMMEDIATE] watchers under matching pattern
} // if event matches
} // for watchers in current target
} // if phase even exists
} // if name even exists
} // phase array
} // for phace integer

} // end propagate

#selected = new Signal('main');
#list = new Signal({
main:[
{ id: 'a', type: 'actor', kind: 'function', dataset: {} },
{id:'b', type:'actor', kind:'function', dataset:{}},
{id:'c', type:'pipe', from:'a:output', to:'b:input', dataset:{}},
],
});

async load(){
console.log('Load something...');
// PLAIN OLD EMITTER SYSTEM //

subscribers = {};
// Event handling methods
on(event, callback) {
// Register a callback for an event
if (!this.subscribers[event]) {
this.subscribers[event] = [];
}
this.subscribers[event].push(callback);
}

once(event, callback) {
// Register a callback that is called at most once
const onceWrapper = (...args) => {
callback(...args);
this.off(event, onceWrapper);
};
this.on(event, onceWrapper);
}

off(event, callback) {
// Unregister a callback for an event
if (!this.subscribers[event]) return;
this.subscribers[event] = this.subscribers[event].filter(cb => cb !== callback);
}

emit(event, ...args) {
// Emit an event, calling all registered callbacks
if (!this.subscribers[event]) return;
// Create a copy of the subscribers array to prevent issues if a callback modifies the subscribers during execution
const subscribers = this.subscribers[event].slice();
for (const callback of subscribers) {
callback(...args);
}
}

// LIFECYCLE SYSTEM

async load(url){
console.log('TODO...');
}

async start(){
console.log('Load XML with savefiles...');
await Promise.all( this.all.filter(o=>o.onStart).map(o=>o.onStart()) )
this.all.map(node=>node.emit('start'));
}

async stop(){

await Promise.all(this.all.filter(o=>o.onStop).map(o=>o.onStop()))
this.all.map(node => node.emit('stop'));
}

}

0 comments on commit 9d60b4c

Please sign in to comment.