puter/src/init_sync.js
KernelDeimos e050506a05 Add class registry (second pass)
ExportService gets removed and instead a global class registry is added.
The `init.js` file is split into `init_sync.js` and `init_async.js`
so that synchronous code that isn't dependent on imports is guarenteed
to run before initgui.js. The globalThis scope and service-script API
now expose `def`, a function for registering class definitions, and
`use`, a function for obtaining registered classes.
2024-05-28 19:06:00 -04:00

119 lines
3.5 KiB
JavaScript

// An initial logger to log do before we get a more fancy logger
// (which we never really do yet, at the time of writing this);
// something like this was also done in backend and it proved useful.
(scope => {
globalThis.logger = {
info: (...a) => console.log('%c[INIT/INFO]', 'color: #4287f5', ...a),
};
})(globalThis);
logger.info('start -> blocking initialization');
// A global promise (like TeePromise, except we can't import anything yet)
// that will be resolved by `init_async.js` when it completes.
(scope => {
scope.init_promise = (() => {
let resolve, reject;
const promise = new Promise((res, rej) => {
resolve = res;
reject = rej;
});
promise.resolve = resolve;
promise.reject = reject;
return promise;
})();
})(globalThis);
// This is where `use()` and `def()` are defined.
//
// A global registry for class definitions. This allows us to expose
// classes to service scripts even when the frontend code is bundled.
// Additionally, it allows us to create hooks upon class registration,
// which we use to turn classes which extend HTMLElement into components
// (i.e. give them tag names because that is required).
//
// It's worth noting `use()` and `def()` for service scripts is exposed
// in initgui.js, in the `launch_services()` function. (at the time this
// comment was written)
(scope => {
const registry_ = {
classes_m: {},
classes_l: [],
hooks_on_register: [],
};
const on_self_registered_api = {
on_other_registered: hook => registry_.hooks_on_register.push(hook),
}
scope.lib = {
is_subclass (subclass, superclass) {
if (subclass === superclass) return true;
let proto = subclass.prototype;
while (proto) {
if (proto === superclass.prototype) return true;
proto = Object.getPrototypeOf(proto);
}
return false;
}
};
scope.def = (cls, id) => {
id = id || cls.ID;
if ( id === undefined ) {
throw new Error('Class must have an ID');
}
if ( registry_.classes_m[id] ) {
throw new Error(`Class with ID ${id} already registered`);
}
registry_.classes_m[id] = cls;
registry_.classes_l.push(cls);
registry_.hooks_on_register.forEach(hook => hook({ cls }));
console.log('registered class', id, registry_);
// Find class that owns 'on_self_registered' hook
let owner = cls;
while (
owner.__proto__ && owner.__proto__.on_self_registered
&& owner.__proto__.on_self_registered === cls.on_self_registered
) {
owner = owner.__proto__;
}
if ( cls.on_self_registered ) {
cls.on_self_registered.call(cls, {
...on_self_registered_api,
is_owner: cls === owner,
});
}
return cls;
};
scope.use = id => {
console.log('use called with id: ', id);
if ( id === undefined ) {
return registry_.classes_m;
}
if ( !registry_.classes_m[id] ) {
throw new Error(`Class with ID ${id} not registered`);
}
console.log(
'okay it\'s going to return:',
registry_.classes_m[id],
'and the entire map is this: ',
registry_.classes_m
)
return registry_.classes_m[id];
}
})(globalThis);
logger.info('end -> blocking initialization');