@zenithcore/core
Version:
Core functionality for ZenithKernel framework
522 lines (434 loc) • 16.9 kB
text/typescript
import { IZenithModule } from "../types";
import { Message } from "../types";
import { Messenger } from "./Messenger";
// MessageHandler type for callback functions
type MessageHandler = (message: any) => void;
type SystemId = string;
import { Scheduler } from "./Scheduler";
import { ECSManager } from "./ECSManager";
import {SystemManager} from "./SystemManager";
import {BaseSystem} from "./BaseSystem";
import {KernelRouter} from "../adapters/KernelRouter";
import type { IslandRegistration, HydrationStrategy } from "../modules/Rendering/types";
import type { HydraContext } from "../lib/hydra-runtime";
export class ZenithKernel {
// Stub methods to be implemented
enableDiagnostics() {
console.log('Diagnostics enabled');
}
enableHotReload() {
console.log('Hot reload enabled');
}
setLogLevel(logLevel: string) {
console.log(`Log level set to: ${logLevel}`);
}
initialize() {
console.log('ZenithKernel initialized');
return this;
}
getSystem(systemId: string): BaseSystem | undefined {
return this.dynamicSystems.get(systemId);
}
startLoop() {
console.log('Kernel loop started');
return this;
}
setOnlineStatus(isOnline: boolean) {
console.log(`Online status set to: ${isOnline}`);
return this;
}
stop() {
console.log('Kernel stopped');
}
private modules = new Map<string, IZenithModule>();
private messenger: Messenger = new Messenger();
private scheduler: Scheduler | undefined;
private ecs = new ECSManager();
private systemManager = new SystemManager(this.ecs);
private dynamicSystems = new Map<string, BaseSystem>();
private router: KernelRouter | undefined;
private islands = new Map<string, IslandRegistration>();
private hydratedIslands = new WeakMap<HTMLElement, { name: string; cleanup?: () => void }>();
private messageHandlers = new Map<string, MessageHandler[]>();
public debug = process.env.NODE_ENV !== "production";
constructor() {
// Initialize messenger
this.messenger.register('kernel');
}
/**
* Get the ECS manager instance
*/
getECS(): ECSManager {
return this.ecs;
}
/**
* Get the router instance if available
*/
getRouter(): KernelRouter | undefined {
return this.router;
}
/**
* Set the router instance
*/
setRouter(router: KernelRouter): void {
this.router = router;
}
/**
* Unregister a system by ID
*/
unregisterSystem(systemId: string): void {
const system: BaseSystem | undefined = this.dynamicSystems.get(systemId);
if (!system) {
console.warn(`⚠️ System "${systemId}" not found.`);
return;
}
this.dynamicSystems.delete(systemId);
this.ecs.removeSystem(system);
if (this.debug) {
console.info(`[ZenithKernel] Unregistered system: ${systemId}`);
}
}
/**
* Hot swap a system with a new implementation
*/
hotSwapSystem(systemId: string, NewCtor: new (...args: any[]) => BaseSystem): void {
this.unregisterSystem(systemId);
const instance = new NewCtor(this);
this.registerSystem(systemId, instance);
if (this.debug) {
console.info(`[ZenithKernel] Hot-swapped system: ${systemId}`);
}
}
/**
* Register a message handler for the specified message type
* @param messageType Type of message to handle
* @param handler Callback function to handle the message
*/
registerMessageHandler(messageType: string, handler: MessageHandler): void {
if (!this.messageHandlers.has(messageType)) {
this.messageHandlers.set(messageType, []);
}
const handlers = this.messageHandlers.get(messageType);
if (handlers) {
handlers.push(handler);
}
if (this.debug) {
console.info(`[ZenithKernel] Registered message handler for type: ${messageType}`);
}
}
/**
* Send a message to a specific target
* @param targetId ID of the message target
* @param message Message to send
*/
send(targetId: string, message: Message): void {
this.messenger.send(targetId, message);
if (this.debug) {
console.info(`[ZenithKernel] Sent message to ${targetId}:`, message);
}
}
/**
* Send a message of the specified type to all registered handlers
* @param messageType Type of message to broadcast
* @param payload Data to include with the message
*/
sendMessage(messageType: string, payload: any = {}): void {
const handlers = this.messageHandlers.get(messageType);
if (handlers && handlers.length > 0) {
const message = { type: messageType, payload, timestamp: Date.now() };
handlers.forEach(handler => {
try {
handler(message);
} catch (error) {
console.error(`[ZenithKernel] Error in message handler for ${messageType}:`, error);
}
});
}
}
/**
* Get list of registered systems
*/
getRegisteredSystems(): string[] {
return Array.from(this.dynamicSystems.keys());
}
/**
* Register an island component for hydration/runtime.
*/
registerIsland(registration: IslandRegistration): void {
if (this.islands.has(registration.name)) {
if (this.debug) {
console.warn(`🔁 Hot-swapping island: ${registration.name}`);
this.unregisterIsland(registration.name);
} else {
throw new Error(`Island "${registration.name}" already registered.`);
}
}
this.islands.set(registration.name, registration);
if (this.debug) {
console.info(`[ZenithKernel] Registered island: ${registration.name}`);
}
}
/**
* Unregister an island component.
*/
unregisterIsland(name: string) {
if (this.islands.has(name)) {
this.islands.delete(name);
if (this.debug) {
console.info(`[ZenithKernel] Unregistered island: ${name}`);
}
}
}
/**
* Get a registered island by name.
*/
getIsland(name: string): IslandRegistration | undefined {
return this.islands.get(name);
}
/**
* Hydrate an island by name and element with enhanced context support.
*/
async hydrateIsland(name: string, element: HTMLElement, props: any = {}, context?: HydraContext): Promise<void> {
const registration = this.getIsland(name);
if (!registration || !registration.component.mount) {
throw new Error(`Island "${name}" not registered or missing mount()`);
}
// Enhanced context with ECS integration
const enhancedContext: HydraContext = {
peerId: context?.peerId || `kernel-${Date.now()}`,
trustLevel: context?.trustLevel || registration.trustLevel,
ecsEntity: context?.ecsEntity,
...context
};
try {
// Call the island's mount function
const cleanup = await registration.component.mount(element, props, enhancedContext);
// Store cleanup function for later
if (typeof cleanup === 'function') {
this.hydratedIslands.set(element, { name, cleanup });
} else {
this.hydratedIslands.set(element, { name });
}
// Mark element as hydrated
element.setAttribute('data-hydra-state', 'hydrated');
element.setAttribute('data-hydra-name', name);
if (this.debug) {
console.info(`[ZenithKernel] Hydrated island: ${name}`);
}
} catch (error) {
element.setAttribute('data-hydra-state', 'error');
if (this.debug) {
console.error(`[ZenithKernel] Failed to hydrate island ${name}:`, error);
}
throw error;
}
}
/**
* Hydrate an island by element ID with auto-discovery.
*/
async hydrateIslandById(elementId: string): Promise<void> {
const element = document.getElementById(elementId);
if (!element) {
throw new Error(`Element with ID ${elementId} not found`);
}
// Auto-discover island configuration from element attributes
const islandName = element.getAttribute('data-zk-island') ||
element.getAttribute('data-hydra-entry');
if (!islandName) {
throw new Error(`No island name found on element ${elementId}`);
}
// Parse props and context from attributes
const propsAttr = element.getAttribute('data-zk-props') ||
element.getAttribute('data-hydra-props');
const contextAttr = element.getAttribute('data-zk-context') ||
element.getAttribute('data-hydra-context');
let props = {};
let context: HydraContext = { peerId: `auto-${elementId}` };
try {
if (propsAttr) props = JSON.parse(propsAttr);
if (contextAttr) context = { ...context, ...JSON.parse(contextAttr) };
} catch (error) {
console.warn('Failed to parse island props/context:', error);
}
return this.hydrateIsland(islandName, element, props, context);
}
/**
* Unmount a hydrated island.
*/
async unmountIsland(element: HTMLElement): Promise<void> {
const islandData = this.hydratedIslands.get(element);
if (!islandData) {
return; // Not a hydrated island
}
// Call cleanup function if available
if (islandData.cleanup) {
try {
await islandData.cleanup();
} catch (error) {
console.warn(`Cleanup failed for island ${islandData.name}:`, error);
}
}
// Call unmount if available
const registration = this.getIsland(islandData.name);
if (registration?.component.unmount) {
try {
await registration.component.unmount(element);
} catch (error) {
console.warn(`Unmount failed for island ${islandData.name}:`, error);
}
}
// Clean up element and tracking
element.removeAttribute('data-hydra-state');
element.removeAttribute('data-hydra-name');
this.hydratedIslands.delete(element);
if (this.debug) {
console.info(`[ZenithKernel] Unmounted island: ${islandData.name}`);
}
}
/**
* List all registered islands.
*/
getRegisteredIslands(): string[] {
return Array.from(this.islands.keys());
}
/**
* Get all registered island registrations.
*/
getAllIslandRegistrations(): IslandRegistration[] {
return Array.from(this.islands.values());
}
/**
* Get all currently hydrated islands.
*/
getHydratedIslands(): { element: HTMLElement; name: string }[] {
const hydrated: { element: HTMLElement; name: string }[] = [];
// Find all elements with hydra state
document.querySelectorAll('[data-hydra-state="hydrated"]').forEach(element => {
const name = element.getAttribute('data-hydra-name');
if (name) {
hydrated.push({ element: element as HTMLElement, name });
}
});
return hydrated;
}
/**
* Auto-discover and hydrate all islands in the DOM.
*/
async discoverAndHydrateIslands(): Promise<void> {
const islandElements = document.querySelectorAll('[data-zk-island], [data-hydra-entry]');
for (const element of islandElements) {
try {
const strategy = element.getAttribute('data-zk-strategy') ||
element.getAttribute('data-hydra-strategy') || 'immediate';
if (strategy === 'immediate' && !element.hasAttribute('data-hydra-state')) {
await this.hydrateIslandById(element.id || `auto-${Date.now()}`);
}
} catch (error) {
console.warn('Failed to auto-hydrate island:', error);
}
}
}
// --- SSR/CLIENT MODE ---
isSSR(): boolean {
return typeof window === "undefined";
}
isClient(): boolean {
return typeof window !== "undefined";
}
/**
* Register a system with the kernel
* @param systemIdOrSystem System ID string or BaseSystem instance
* @param systemInstance Optional BaseSystem instance when ID is provided
*/
registerSystem(systemIdOrSystem: string | BaseSystem, systemInstance?: BaseSystem): void {
// If first parameter is a string (system ID) and second parameter is a system instance
if (typeof systemIdOrSystem === 'string' && systemInstance) {
const systemId = systemIdOrSystem;
// Check if system with this ID already exists
if (this.dynamicSystems.has(systemId)) {
if (this.debug) {
console.warn(`🔁 Hot-swapping system: ${systemId}`);
this.unregisterSystem(systemId);
} else {
throw new Error(`System "${systemId}" already registered.`);
}
}
this.dynamicSystems.set(systemId, systemInstance);
this.ecs.registerSystem(systemInstance);
if (this.debug) {
console.info(`[ZenithKernel] Registered system with ID: ${systemId}`);
}
return;
}
// If first parameter is a system instance
if (typeof systemIdOrSystem !== 'string') {
const system = systemIdOrSystem;
const id = system.constructor.name;
// Check if system with this ID already exists
if (this.dynamicSystems.has(id)) {
if (this.debug) {
console.warn(`🔁 Hot-swapping system: ${id}`);
this.unregisterSystem(id);
} else {
throw new Error(`System "${id}" already registered.`);
}
}
this.dynamicSystems.set(id, system);
this.ecs.registerSystem(system);
// Log registration
if (this.debug) {
console.info(`✅ Registered system: ${id}`);
}
}
}
init() {
this.systemManager.init();
this.ecs = new ECSManager();
this.ecs.setKernel(this); // 🔁 connect kernel back into ECS
this.scheduler = Scheduler.getInstance(this.ecs);
}
update() {
this.scheduler?.tick();
this.systemManager.update();
}
registerModule(module: IZenithModule) {
if (this.modules.has(module.id)) {
throw new Error(`Module ${module.id} already registered`);
}
this.modules.set(module.id, module);
this.messenger?.register(module.id);
// @ts-ignore
module.onLoad(this);
}
unregisterModule(id: string) {
const module = this.modules.get(id);
if (!module) return;
module.onUnload?.();
this.modules.delete(id);
this.messenger?.unregister(id);
this.scheduler?.unschedule(id);
}
hotSwapModule(module: IZenithModule) {
this.unregisterModule(module.id);
this.registerModule(module);
}
// Receive messages - kept for backward compatibility
receive(id: string): Message[] | undefined {
return this.messenger?.receive(id);
}
schedule(id: string, generatorFactory: () => Generator) {
this.scheduler?.schedule(id, generatorFactory);
}
getModule<T extends IZenithModule>(id: string): T | undefined {
return this.modules.get(id) as T;
}
getECS(): ECSManager {
return this.ecs;
}
async loadWasmModule(path: string): Promise<WebAssembly.Exports> {
const wasmBuffer = await fetch(path).then(res => res.arrayBuffer());
const wasmModule = await WebAssembly.instantiate(wasmBuffer, {});
return wasmModule.instance.exports;
}
// setRouter is already defined earlier in the class
}