Initial Phoenix analyzer
This commit is contained in:
@@ -0,0 +1,228 @@
|
||||
// core/registry.js — Lazy Loader + Safe Runner für Meter
|
||||
// Lädt Meter-Module on-demand (oder via registerMeter) und kapselt Fehler.
|
||||
|
||||
const cache = {
|
||||
meters: new Map(), // id -> module
|
||||
};
|
||||
|
||||
const meterRenderCache = new Map(); // key -> surface
|
||||
const METER_CACHE_MARGIN = 32;
|
||||
let activeFrameStamp = 0;
|
||||
|
||||
const CAN_USE_OFFSCREEN = typeof OffscreenCanvas === 'function';
|
||||
|
||||
function createCacheSurface(width, height) {
|
||||
const w = Math.max(1, Math.ceil(width));
|
||||
const h = Math.max(1, Math.ceil(height));
|
||||
if (CAN_USE_OFFSCREEN) {
|
||||
const canvas = new OffscreenCanvas(w, h);
|
||||
const ctx = canvas.getContext('2d');
|
||||
if (ctx) return { canvas, ctx, width: w, height: h, stamp: -1 };
|
||||
}
|
||||
if (typeof document !== 'undefined') {
|
||||
const canvas = document.createElement('canvas');
|
||||
canvas.width = w;
|
||||
canvas.height = h;
|
||||
const ctx = canvas.getContext('2d');
|
||||
if (ctx) return { canvas, ctx, width: w, height: h, stamp: -1 };
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function cacheKeyForMeter(id, rect) {
|
||||
const x = Math.round(Number(rect?.x) || 0);
|
||||
const y = Math.round(Number(rect?.y) || 0);
|
||||
const w = Math.max(1, Math.round(Number(rect?.w) || 0));
|
||||
const h = Math.max(1, Math.round(Number(rect?.h) || 0));
|
||||
return `${id}::${x},${y},${w}x${h}`;
|
||||
}
|
||||
|
||||
function ensureCacheSurface(id, rect) {
|
||||
const key = cacheKeyForMeter(id, rect);
|
||||
let surface = meterRenderCache.get(key);
|
||||
if (!surface) {
|
||||
surface = createCacheSurface(rect.w + METER_CACHE_MARGIN * 2, rect.h + METER_CACHE_MARGIN * 2);
|
||||
if (!surface) return null;
|
||||
meterRenderCache.set(key, surface);
|
||||
}
|
||||
if (!surface.canvas || !surface.ctx) return null;
|
||||
const w = Math.max(1, Math.ceil(rect.w + METER_CACHE_MARGIN * 2));
|
||||
const h = Math.max(1, Math.ceil(rect.h + METER_CACHE_MARGIN * 2));
|
||||
if (surface.canvas.width !== w || surface.canvas.height !== h) {
|
||||
surface.canvas.width = w;
|
||||
surface.canvas.height = h;
|
||||
surface.stamp = -1;
|
||||
}
|
||||
surface.width = w;
|
||||
surface.height = h;
|
||||
surface.margin = METER_CACHE_MARGIN;
|
||||
return surface;
|
||||
}
|
||||
|
||||
export async function loadMeter(id) {
|
||||
if (!id) throw new Error('loadMeter: leere Meter-ID');
|
||||
if (cache.meters.has(id)) return cache.meters.get(id);
|
||||
const mod = await import(`../meters/${id}.js`);
|
||||
cache.meters.set(id, mod);
|
||||
return mod;
|
||||
}
|
||||
|
||||
// --- Meter-Facade -----------------------------------------------------------
|
||||
// Views rufen nur diese Fassade, kennen also die Module/States nicht direkt.
|
||||
const meterStates = new Map(); // instanceKey -> { id, shared }
|
||||
const DEFAULT_INSTANCE_SUFFIX = '::default';
|
||||
|
||||
function makeMeterInstanceKey(id, rect) {
|
||||
if (!rect) return `${id}::default`;
|
||||
const x = Math.round(Number(rect.x) || 0);
|
||||
const y = Math.round(Number(rect.y) || 0);
|
||||
const w = Math.round(Number(rect.w) || 0);
|
||||
const h = Math.round(Number(rect.h) || 0);
|
||||
return `${id}::${x},${y},${w},${h}`;
|
||||
}
|
||||
|
||||
function makeDefaultMeterInstanceKey(id) {
|
||||
return `${id}${DEFAULT_INSTANCE_SUFFIX}`;
|
||||
}
|
||||
|
||||
function getMeterSharedById(id) {
|
||||
const matches = [];
|
||||
for (const entry of meterStates.values()) {
|
||||
if (entry?.id === id && entry.shared) matches.push(entry.shared);
|
||||
}
|
||||
return matches;
|
||||
}
|
||||
|
||||
function getPreferredMeterShared(id) {
|
||||
const defaultEntry = meterStates.get(makeDefaultMeterInstanceKey(id));
|
||||
if (defaultEntry?.shared) return defaultEntry.shared;
|
||||
for (const entry of meterStates.values()) {
|
||||
if (entry?.id === id && entry.shared) return entry.shared;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
async function ensureMeter(id, config, rect) {
|
||||
const mod = await loadMeter(id);
|
||||
const instanceKey = makeMeterInstanceKey(id, rect);
|
||||
if (!meterStates.has(instanceKey)) {
|
||||
const shared = mod.initShared?.(config) || {};
|
||||
meterStates.set(instanceKey, { id, shared });
|
||||
}
|
||||
return { mod, instanceKey, shared: meterStates.get(instanceKey)?.shared || null };
|
||||
}
|
||||
|
||||
async function ensureDefaultMeterShared(id, config) {
|
||||
const mod = await loadMeter(id);
|
||||
const instanceKey = makeDefaultMeterInstanceKey(id);
|
||||
if (!meterStates.has(instanceKey)) {
|
||||
const shared = mod.initShared?.(config) || {};
|
||||
meterStates.set(instanceKey, { id, shared });
|
||||
}
|
||||
return { mod, instanceKey, shared: meterStates.get(instanceKey)?.shared || null };
|
||||
}
|
||||
|
||||
export const meterFacade = {
|
||||
async update(packet, config, activeIds) {
|
||||
const seen = new Set();
|
||||
for (const rawId of activeIds || []) {
|
||||
if (!rawId || seen.has(rawId)) continue;
|
||||
seen.add(rawId);
|
||||
const id = rawId;
|
||||
try {
|
||||
const { mod } = await ensureDefaultMeterShared(id, config);
|
||||
const sharedList = getMeterSharedById(id);
|
||||
for (const shared of sharedList) {
|
||||
mod.update?.(packet, shared);
|
||||
}
|
||||
} catch (e) {
|
||||
// ein defektes Meter wird übersprungen, andere laufen weiter
|
||||
console.warn(`Meter ${id} update error:`, e);
|
||||
}
|
||||
}
|
||||
},
|
||||
async pointer(evt, rect, id, config) {
|
||||
if (!id) return false;
|
||||
try {
|
||||
const { mod, shared } = await ensureMeter(id, config, rect);
|
||||
if (typeof mod.pointer === 'function') {
|
||||
const handled = await mod.pointer(evt, rect, config, shared);
|
||||
return !!handled;
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn(`Meter ${id} pointer error:`, e);
|
||||
}
|
||||
return false;
|
||||
},
|
||||
async draw(ctx, rect, id, config) {
|
||||
try {
|
||||
const { mod, shared } = await ensureMeter(id, config, rect);
|
||||
const allowCache = !(mod && mod.disableCache);
|
||||
if (!allowCache) {
|
||||
await mod.draw?.(ctx, rect, config, shared);
|
||||
return;
|
||||
}
|
||||
const cacheSurface = ensureCacheSurface(id, rect);
|
||||
const stamp = activeFrameStamp;
|
||||
const margin = cacheSurface ? cacheSurface.margin || METER_CACHE_MARGIN : 0;
|
||||
const drawX = rect.x - margin;
|
||||
const drawY = rect.y - margin;
|
||||
if (cacheSurface && cacheSurface.stamp === stamp) {
|
||||
ctx.drawImage(cacheSurface.canvas, drawX, drawY);
|
||||
return;
|
||||
}
|
||||
if (cacheSurface && cacheSurface.ctx) {
|
||||
cacheSurface.ctx.save();
|
||||
try {
|
||||
cacheSurface.ctx.setTransform(1, 0, 0, 1, 0, 0);
|
||||
cacheSurface.ctx.clearRect(0, 0, cacheSurface.canvas.width, cacheSurface.canvas.height);
|
||||
cacheSurface.ctx.font = ctx.font;
|
||||
cacheSurface.ctx.textAlign = ctx.textAlign;
|
||||
cacheSurface.ctx.textBaseline = ctx.textBaseline;
|
||||
cacheSurface.ctx.translate(-drawX, -drawY);
|
||||
await mod.draw?.(cacheSurface.ctx, rect, config, shared);
|
||||
} finally {
|
||||
cacheSurface.ctx.restore();
|
||||
}
|
||||
cacheSurface.stamp = stamp;
|
||||
ctx.drawImage(cacheSurface.canvas, drawX, drawY);
|
||||
return;
|
||||
}
|
||||
await mod.draw?.(ctx, rect, config, shared);
|
||||
} catch (e) {
|
||||
// Slot neutral darstellen
|
||||
ctx.save();
|
||||
ctx.strokeStyle = 'rgba(200,80,80,.8)';
|
||||
ctx.setLineDash([6,4]);
|
||||
ctx.strokeRect(rect.x+0.5, rect.y+0.5, rect.w-1, rect.h-1);
|
||||
ctx.setLineDash([]);
|
||||
ctx.fillStyle = '#ffdddd';
|
||||
ctx.textAlign = 'center';
|
||||
ctx.fillText(`Meter "${id}" defekt`, rect.x + rect.w/2, rect.y + rect.h/2);
|
||||
ctx.textAlign = 'start';
|
||||
ctx.restore();
|
||||
}
|
||||
},
|
||||
setFrameStamp(stamp) {
|
||||
activeFrameStamp = stamp || 0;
|
||||
},
|
||||
getState(id) {
|
||||
return getPreferredMeterShared(id);
|
||||
},
|
||||
invalidateAll() {
|
||||
meterStates.clear();
|
||||
meterRenderCache.clear();
|
||||
},
|
||||
};
|
||||
|
||||
if (typeof window !== 'undefined') {
|
||||
window.__AN_REGISTRY__ = window.__AN_REGISTRY__ || {};
|
||||
window.__AN_REGISTRY__.invalidateAll = () => meterFacade.invalidateAll();
|
||||
}
|
||||
|
||||
// --- Meter Registration System ---
|
||||
export function registerMeter(meterModule) {
|
||||
if (meterModule && meterModule.id) {
|
||||
cache.meters.set(meterModule.id, Promise.resolve(meterModule));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user