@utsp/runtime-client
Version:
UTSP Runtime Client - Local and multi-user client runtime
2 lines (1 loc) • 27.6 kB
JavaScript
"use strict";var w=Object.defineProperty;var H=Object.getOwnPropertyDescriptor;var G=Object.getOwnPropertyNames;var N=Object.prototype.hasOwnProperty;var z=(c,e,t)=>e in c?w(c,e,{enumerable:!0,configurable:!0,writable:!0,value:t}):c[e]=t;var f=(c,e)=>w(c,"name",{value:e,configurable:!0});var q=(c,e)=>{for(var t in e)w(c,t,{get:e[t],enumerable:!0})},W=(c,e,t,n)=>{if(e&&typeof e=="object"||typeof e=="function")for(let i of G(e))!N.call(c,i)&&i!==t&&w(c,i,{get:()=>e[i],enumerable:!(n=H(e,i))||n.enumerable});return c};var j=c=>W(w({},"__esModule",{value:!0}),c);var o=(c,e,t)=>(z(c,typeof e!="symbol"?e+"":e,t),t);var _={};q(_,{AudioManager:()=>B.AudioManager,ClientRuntime:()=>A,RendererType:()=>x});module.exports=j(_);var E=require("@utsp/core"),C=require("@utsp/render"),y=require("@utsp/input"),O=require("@utsp/network-client"),D=require("@utsp/audio");var x=(t=>(t.TerminalGL="webgl",t.Terminal2D="terminal2d",t))(x||{});var F=class F{constructor(e=60){o(this,"frameCount",0);o(this,"fps",0);o(this,"fpsUpdateTime",0);o(this,"lastCoreTime",0);o(this,"lastRenderTime",0);o(this,"lastTotalTime",0);o(this,"coreTimeSamples",[]);o(this,"renderTimeSamples",[]);o(this,"totalTimeSamples",[]);o(this,"maxSamples");if(e<=0)throw new Error("maxSamples must be positive");this.maxSamples=e}startTracking(e){this.fpsUpdateTime=e,this.frameCount=0,this.fps=0}updateFPS(e){this.frameCount++,e-this.fpsUpdateTime>=1e3&&(this.fps=Math.round(this.frameCount*1e3/(e-this.fpsUpdateTime)),this.frameCount=0,this.fpsUpdateTime=e)}recordFrameTiming(e,t){let n=e+t;this.lastCoreTime=e,this.lastRenderTime=t,this.lastTotalTime=n,this.coreTimeSamples.push(e),this.renderTimeSamples.push(t),this.totalTimeSamples.push(n),this.coreTimeSamples.length>this.maxSamples&&(this.coreTimeSamples.shift(),this.renderTimeSamples.shift(),this.totalTimeSamples.shift())}getFPS(){return this.fps}getLastFrameTiming(){if(!(this.lastCoreTime===0&&this.lastRenderTime===0))return{coreTime:this.lastCoreTime,renderTime:this.lastRenderTime,totalTime:this.lastTotalTime}}getAverageFrameTiming(){if(this.coreTimeSamples.length!==0)return{coreTime:this.average(this.coreTimeSamples),renderTime:this.average(this.renderTimeSamples),totalTime:this.average(this.totalTimeSamples)}}getStats(){return{fps:this.fps,lastFrame:this.getLastFrameTiming(),avgFrame:this.getAverageFrameTiming(),sampleCount:this.coreTimeSamples.length,maxSamples:this.maxSamples}}reset(){this.frameCount=0,this.fps=0,this.fpsUpdateTime=0,this.lastCoreTime=0,this.lastRenderTime=0,this.lastTotalTime=0,this.coreTimeSamples=[],this.renderTimeSamples=[],this.totalTimeSamples=[]}average(e){return e.length===0?0:e.reduce((t,n)=>t+n,0)/e.length}};f(F,"PerformanceMonitor");var M=F;var U=require("@utsp/core");var P=class P{constructor(e,t={}){o(this,"renderer");o(this,"options");this.renderer=e,this.options={debug:t.debug??!1,useImageDataRendering:t.useImageDataRendering??!1}}async initialize(e){await this.loadDefaultFont(e),await this.waitForReady()}async waitForReady(e=100,t=50){this.log("Waiting for renderer to be ready");let n=0;return new Promise((i,r)=>{let s=f(()=>{if(n++,this.renderer.isReady())this.log(`Renderer ready after ${n*t}ms`),i();else if(n>=e){let l=`Renderer failed to be ready after ${e*t}ms`;this.log(l),r(new Error(l))}else setTimeout(s,t)},"check");s()})}async loadDefaultFont(e){this.log("Loading ASCII 8x8 font");let t=(0,U.createASCII8x8FontLoad)(1);e.hasBitmapFont(t.fontId)||e.loadBitmapFontById(t.fontId,{charWidth:t.width,charHeight:t.height,cellWidth:t.cellWidth,cellHeight:t.cellHeight,glyphs:new Map(t.characters.map(n=>[n.charCode,n.bitmap]))}),"setImageDataRendering"in this.renderer&&this.options.useImageDataRendering&&(this.renderer.setImageDataRendering(!0),this.log("ImageData rendering enabled")),this.log("Font loaded and event triggered")}render(e,t){if(!this.renderer.isReady())return console.warn("[RENDERER MANAGER] Renderer not ready"),!1;let n=e.getRenderState(t);if(!n||n.displays.length===0)return console.warn("[RENDERER MANAGER] No render state or no displays"),!1;let i=n.displays[0];return this.renderer.renderDisplayData(i),!0}getRenderer(){return this.renderer}getCanvas(){return this.renderer.getCanvas()}isReady(){return this.renderer.isReady()}destroy(){this.log("Destroying renderer"),this.renderer.destroy()}log(e){this.options.debug&&console.warn(`[RendererManager] ${e}`)}};f(P,"RendererManager");var S=P;var R=require("@utsp/input");var $=class ${constructor(e,t,n,i,r){o(this,"network");o(this,"core");o(this,"rendererManager");o(this,"performanceMonitor");o(this,"options");o(this,"audioManager",null);o(this,"userId","");o(this,"lastReceivedTick",-1);o(this,"connected",!1);this.network=e,this.core=t,this.rendererManager=n,this.performanceMonitor=i,this.options={serverUrl:r.serverUrl,username:r.username,token:r.token??"",debug:r.debug??!1,autoReconnect:r.autoReconnect??!0,canvasWidth:r.canvasWidth,canvasHeight:r.canvasHeight}}async connect(){this.log(`Connecting to ${this.options.serverUrl}`),await this.network.connect(),this.connected=!0,this.log("Connected to server"),this.setupNetworkHandlers()}async joinGame(e){return new Promise((t,n)=>{this.network.send("join",{username:this.options.username,token:this.options.token}),this.network.on("join_response",i=>{if(i.success){this.userId=i.userId,this.log(`Joined game as ${this.userId}`);let r=this.core.createUser(this.userId,this.options.username);this.audioManager&&r.setAudioProcessor(this.audioManager),e.initUser(this.core,r,{username:this.options.username,token:this.options.token}),t(this.userId)}else n(new Error(i.error||"Failed to join game"))}),setTimeout(()=>n(new Error("Join timeout")),5e3)})}sendInput(e){let t=this.core.getUser(this.userId);if(!t)return;let n=this.rendererManager.getCanvas();if(n){let p=this.rendererManager.getRenderer()?.getOffsets?.(),T={offsetX:p?.offsetX??0,offsetY:p?.offsetY??0},v=R.InputCollector.collectMousePosition(e,n,this.options.canvasWidth,this.options.canvasHeight,T);t.setMousePosition(v.x,v.y,v.over),R.InputCollector.collectTouchPositions(e,n,this.options.canvasWidth,this.options.canvasHeight,10,T).forEach(I=>{t.setTouchPosition(I.id,I.x,I.y,I.over)})}let i=t.getInputBindingRegistry(),r=i.getAllAxes(),s=i.getAllButtons(),d=R.InputCollector.collectAxisSources(r,e),l=R.InputCollector.collectButtonSources(s,e),a=R.InputCollector.collectTextInputs(e);a.length>0&&t.setTextInputs(a);let u={};r.forEach(m=>{let p=i.evaluateAxis(m.bindingId,d);u[m.name]=p,t.setAxis(m.name,p)});let h={};s.forEach(m=>{let p=i.evaluateButton(m.bindingId,l);h[m.name]=p,t.setButton(m.name,p)});let g=t.getMouseDisplayInfo(),b=g?{x:g.localX,y:g.localY}:null;this.network.send("input",{timestamp:Date.now(),axes:u,buttons:h,mousePosition:b,textInputs:a})}disconnect(){this.connected&&(this.network.send("leave",{}),this.network.disconnect(),this.connected=!1,this.log("Disconnected from server"))}getUserId(){return this.userId}isConnected(){return this.connected}destroy(){this.disconnect(),this.network.destroy(),this.log("NetworkSync destroyed")}setupNetworkHandlers(){this.network.on("update",e=>{try{let t=this.convertToUint8Array(e,"update");if(!t)return;this.core.applyUpdatePacketBuffer(this.userId,t)?this.rendererManager.render(this.core,this.userId):this.log("Failed to apply update packet")}catch(t){this.log(`Error applying update packet: ${t}`)}}),this.network.on("update-static",e=>{this.handleUpdatePacket(e,"update-static")}),this.network.on("update-dynamic",e=>{this.handleUpdatePacket(e,"update-dynamic")}),this.network.on("load",e=>{try{let t=this.convertToUint8Array(e,"load");if(!t)return;this.core.applyLoadPacket(t)?this.log("Load packet applied successfully"):this.log("Failed to apply load packet")}catch(t){this.log(`Error applying load packet: ${t}`)}}),this.network.on("input-bindings",e=>{this.core.applyInputBindingsLoadPacket(this.userId,e)?this.log("Input bindings configured"):this.log("Failed to apply input bindings")}),this.audioManager&&this.setupSoundHandlers(),this.network.on("disconnect",()=>{this.connected=!1,this.log("Disconnected from server")})}handleUpdatePacket(e,t){try{let n=this.convertToUint8Array(e,t);if(!n)return;let i=new DataView(n.buffer,n.byteOffset,n.byteLength),r=Number(i.getBigUint64(0,!1));if(t==="update-dynamic"&&r<this.lastReceivedTick)return;if(r>this.lastReceivedTick&&(this.lastReceivedTick=r),this.core.applyUpdatePacketBuffer(this.userId,n)){console.warn(`[CLIENT] ${t}: ${n.length}B (tick ${r}) - APPLIED`),this.options.debug&&this.debugRenderState();let d=performance.now();this.rendererManager.render(this.core,this.userId);let l=performance.now()-d;this.performanceMonitor.recordFrameTiming(0,l),this.options.debug&&console.warn(`[CLIENT] render() completed for ${t} (${l.toFixed(2)}ms)`)}else console.warn(`[CLIENT] Failed to apply ${t} packet (tick ${r})`)}catch(n){this.log(`Error applying ${t} update packet: ${n}`),console.error(n)}}convertToUint8Array(e,t){return e instanceof Uint8Array?e:e instanceof ArrayBuffer?new Uint8Array(e):Array.isArray(e)?new Uint8Array(e):e.data&&Array.isArray(e.data)?new Uint8Array(e.data):typeof e=="object"&&e.type==="Buffer"?new Uint8Array(e.data):(this.log(`Unknown data type for ${t} packet: ${typeof e}`),null)}debugRenderState(){let e=this.core.getUser(this.userId);if(e){let n=e.getLayers();console.warn(`[CLIENT] User has ${n.length} layers`),n.forEach((i,r)=>{let s=i.getOrders(),d=i.getStatic();(s.length>0||d)&&console.warn(` Layer ${r}: ${s.length} orders, static=${d}, z=${i.getZOrder()}`)})}let t=this.rendererManager.isReady();console.warn(`[CLIENT] Renderer ready: ${t}`)}log(e){this.options.debug&&console.warn(`[NetworkSync] ${e}`)}setAudioManager(e){if(this.audioManager=e,e&&this.userId){let t=this.core.getUser(this.userId);t&&t.setAudioProcessor(e)}this.connected&&e&&this.setupSoundHandlers()}setupSoundHandlers(){this.audioManager&&(this.network.on("sound-load",async e=>{e.mode==="file"?await this.handleSoundLoad(e):e.mode==="external"&&await this.handleSoundExternalLoad(e)}),this.network.on("sound-play",e=>{let t=this.core.getUser(this.userId);t&&(t.applyAudioCommands([e]),this.log(`Playing sound: ${e.sound} [#${e.instanceId??"?"}]`))}),this.network.on("sound-fadeout",e=>{let t=this.core.getUser(this.userId);t&&(t.applyAudioCommands([e]),this.log(`Fading out sound: ${e.sound} over ${e.duration}s`))}),this.network.on("sound-pause",e=>{let t=this.core.getUser(this.userId);t&&(t.applyAudioCommands([e]),this.log(`Paused sound: ${e.sound}`))}),this.network.on("sound-resume",e=>{let t=this.core.getUser(this.userId);t&&(t.applyAudioCommands([e]),this.log(`Resumed sound: ${e.sound}`))}),this.network.on("sound-stop",e=>{let t=this.core.getUser(this.userId);t&&(t.applyAudioCommands([e]),this.log(`Stopped sound: ${e.sound}`))}),this.network.on("audio-config",e=>{let t=this.core.getUser(this.userId);t&&(t.applyAudioConfigCommands([e]),this.log(`Audio config applied: ${e.type}`))}),this.log("Sound handlers registered"))}async handleSoundLoad(e){if(!this.audioManager){this.log("Cannot load sounds: AudioManager not initialized");return}let t=this.audioManager.getSoundBank();if(!t){this.log("Cannot load sounds: SoundBank not initialized");return}for(let n of e.sounds)try{await t.loadFromData(n.soundId,n.name,n.data),this.log(`Loaded sound: ${n.name} (${n.data.length} bytes)`)}catch(i){console.error(`[NetworkSync] Failed to load sound "${n.name}":`,i)}}async handleSoundExternalLoad(e){if(!this.audioManager){this.log("Cannot load external sounds: AudioManager not initialized");return}let t=this.audioManager.getSoundBank();if(!t){this.log("Cannot load external sounds: SoundBank not initialized");return}for(let n of e.sounds)try{await t.loadFromUrl(n.soundId,n.name,n.url),this.log(`Loaded external sound: ${n.name} from ${n.url}`),this.network.send("sound-ack",{soundId:n.soundId,status:"loaded"})}catch(i){console.error(`[NetworkSync] Failed to load external sound "${n.name}":`,i),this.network.send("sound-ack",{soundId:n.soundId,status:"error",error:String(i)})}}};f($,"NetworkSync");var k=$;var L=class L{constructor(e){o(this,"core");o(this,"rendererManager");o(this,"rendererType");o(this,"input",null);o(this,"networkSync",null);o(this,"options");o(this,"running",!1);o(this,"startTime",0);o(this,"lastTimestamp",0);o(this,"userId","");o(this,"mode");o(this,"visibilityChangeHandler");o(this,"tickRate",30);o(this,"accumulatedTime",0);o(this,"FRAME_TIME_MIN",1e3/60);o(this,"lastRenderTimestamp",0);o(this,"rafId",0);o(this,"performanceMonitor");o(this,"renderMode","continuous");o(this,"renderRequested",!1);o(this,"autoplayOverlay",null);o(this,"autoplay",!0);o(this,"audioManager",null);this.mode=e.mode,e.mode==="local"?(this.options={mode:"local",application:e.application,container:e.container,debug:e.debug??!1,width:e.width??80,height:e.height??25,userId:e.userId??"local",username:e.username??"User",showGrid:e.showGrid??!1,renderer:e.renderer??"webgl",useImageDataRendering:e.useImageDataRendering??!0,mobileInputConfig:e.mobileInputConfig,captureInput:e.captureInput??!1,inputEnabled:e.inputEnabled??!0,autoplay:e.autoplay??!0,autoplayOptions:e.autoplayOptions},this.userId=e.userId??"local",this.autoplay=e.autoplay??!0):(this.options={mode:"connected",application:e.application,container:e.container,serverUrl:e.serverUrl,username:e.username??"Player",debug:e.debug??!1,width:e.width??80,height:e.height??25,autoReconnect:e.autoReconnect??!0,token:e.token,showGrid:e.showGrid??!1,renderer:e.renderer??"webgl",useImageDataRendering:e.useImageDataRendering??!0,mobileInputConfig:e.mobileInputConfig,captureInput:e.captureInput??!1,inputEnabled:e.inputEnabled??!0,autoplay:e.autoplay??!0,autoplayOptions:e.autoplayOptions},this.autoplay=e.autoplay??!0),this.log(`Initializing ClientRuntime (${this.mode} mode)`),this.core=new E.Core({mode:"client",maxUsers:this.mode==="local"?1:100}),this.core.onPaletteChanged(n=>{this.onCorePaletteChanged(n)}),this.core.onBitmapFontChanged(n=>{this.onCoreBitmapFontChanged(n)}),this.visibilityChangeHandler=()=>{!document.hidden&&this.running&&(this.lastTimestamp=performance.now(),this.accumulatedTime=0,this.log("Tab visible: Reset timing"))},document.addEventListener("visibilitychange",this.visibilityChangeHandler);let t=this.createRenderer();if(this.rendererType=this.options.renderer??"webgl",this.rendererManager=new S(t,{debug:this.options.debug,useImageDataRendering:this.options.useImageDataRendering}),this.options.inputEnabled?this.input=new y.UnifiedInputRouter({enableKeyboardMouse:!0,enableGamepad:!0,enableMobile:!0,targetElement:window,mobileTargetElement:void 0,debug:this.options.debug,keyboardConfig:{preventDefault:this.options.captureInput??!1,stopPropagation:this.options.captureInput??!1},mouseConfig:{preventDefault:this.options.captureInput??!1,stopPropagation:this.options.captureInput??!1},mobileConfig:{preventDefault:this.options.mobileInputConfig?.preventDefault??!0,passive:this.options.mobileInputConfig?.passive??!1,maxTouches:this.options.mobileInputConfig?.maxTouches??10}}):this.log("Input disabled (inputEnabled: false)"),this.performanceMonitor=new M(60),this.mode==="connected"){let n=this.options,i=new O.SocketIOClient({url:n.serverUrl,autoReconnect:n.autoReconnect,auth:{username:n.username,token:n.token},debug:this.options.debug});this.networkSync=new k(i,this.core,this.rendererManager,this.performanceMonitor,{serverUrl:n.serverUrl,username:n.username,token:n.token,debug:this.options.debug,autoReconnect:n.autoReconnect,canvasWidth:this.options.width,canvasHeight:this.options.height})}this.tickRate=e.tickRate??30,this.tickRate===0&&!e.renderMode?(this.renderMode="on-demand",this.log("tickRate is 0: automatically enabling on-demand render mode")):this.renderMode=e.renderMode??"continuous",this.log(`Configured: tickRate=${this.tickRate}, renderMode=${this.renderMode}`),this.log("ClientRuntime initialized")}getMode(){return this.mode}getRendererType(){return this.rendererType}isRunning(){return this.running}createRenderer(){if((this.options.renderer??"webgl")==="terminal2d"){this.log("Creating Terminal 2D renderer");let r=new C.Terminal2D(this.options.container,{fixedCols:this.options.width,fixedRows:this.options.height,cellAspectRatio:1,showDebugGrid:this.options.showGrid});return this.options.useImageDataRendering&&(this.log("Enabling ImageData rendering (pixel-perfect, optimized)"),r.setImageDataRendering(!0)),this.log("Canvas 2D renderer created successfully"),r}this.log("Creating TerminalGL renderer");let n=document.createElement("canvas").getContext("webgl");if(!n)throw new Error("WebGL not supported. TerminalGL requires WebGL 1.0.");if(!n.getExtension("OES_element_index_uint"))throw new Error("OES_element_index_uint extension not supported. TerminalGL requires this extension for large terminals (256x256). Supported on 97% of devices (Android 4.3+, iOS 8+, Desktop).");if(!(this.options.container instanceof HTMLDivElement))throw new Error(`TerminalGL requires container to be an HTMLDivElement. Received: ${this.options.container.tagName}`);let i=new C.TerminalGL(this.options.container,{cols:this.options.width,rows:this.options.height,charWidth:8,charHeight:8,showGrid:this.options.showGrid});return this.log("TerminalGL created successfully"),i}setTickRate(e){if(e<0||e>1e3)throw new Error(`Invalid tick rate: ${e}. Must be between 0 and 1000.`);if(this.mode==="connected"){this.log("setTickRate() has no effect in connected mode");return}this.tickRate=e,e===0&&this.renderMode==="continuous"?(this.renderMode="on-demand",this.log(`Tick rate set to ${e} TPS (update loop disabled, automatically switched to on-demand render mode)`)):this.log(`Tick rate set to ${e} TPS ${e===0?"(update loop disabled)":""}`)}setRenderMode(e){this.renderMode=e,this.log(`Render mode set to ${e}`)}setMaxFPS(e){if(e<=0||e>240)throw new Error(`Invalid FPS: ${e}. Must be between 1 and 240.`);this.FRAME_TIME_MIN=1e3/e,this.log(`Max FPS set to ${e}`)}getTickRate(){return this.tickRate}requestRender(){this.renderMode==="on-demand"?(this.renderRequested=!0,this.log("Render requested")):this.log("requestRender() has no effect in continuous mode")}async start(){if(this.running){this.log("Already running");return}this.log("Starting ClientRuntime"),this.audioManager=new D.AudioManager({debug:this.options.debug}),this.autoplay?(this.log("Autoplay enabled, initializing AudioManager..."),this.audioManager.initialize()):(this.log("Autoplay disabled, showing overlay..."),await new Promise(n=>{this.autoplayOverlay=new C.AutoplayOverlay(this.options.container,{...this.options.autoplayOptions,onStart:()=>{this.log("User clicked start button"),this.audioManager&&(this.audioManager.initialize(),this.audioManager.playStartSound()),n()}})}),this.log("Autoplay overlay dismissed, continuing startup...")),await this.rendererManager.initialize(this.core),this.log("Renderer is ready"),this.onCorePaletteChanged(this.core.getPalette()),this.log("Initial palette sent to renderer"),this.log("Calling application.init()"),await this.options.application.init(this.core,this);let e=this.rendererManager.getCanvas();e&&this.input&&this.input.setMobileTarget(e),this.mode==="connected"&&this.networkSync?(this.log("Connecting to server"),this.audioManager&&this.networkSync.setAudioManager(this.audioManager),await this.networkSync.connect(),this.userId=await this.networkSync.joinGame(this.options.application)):await this.createLocalUser();let t=this.core.getUser(this.userId);if(t){let n=t.getDisplays();if(n.length>0){let r=n[0].getSize(),s=r.x,d=r.y,l=this.rendererManager.getRenderer(),a,u;if(this.rendererType==="webgl"){let g=l.getGridSize();a=g.cols,u=g.rows}else{let h=l;a=h.getCols(),u=h.getRows()}(a!==s||u!==d)&&(this.log(`Adjusting renderer from ${a}\xD7${u} to match display ${s}\xD7${d}`),l.resize(s,d))}}this.input&&this.input.start(),this.running=!0,this.startTime=performance.now(),this.lastTimestamp=this.startTime,this.lastRenderTimestamp=this.startTime,this.accumulatedTime=0,this.performanceMonitor.reset(),this.performanceMonitor.startTracking(this.startTime),this.log("Starting main loop"),this.mainLoop(this.lastTimestamp)}async stop(){if(!this.running)return;this.log("Stopping ClientRuntime"),this.running=!1,this.rafId&&(cancelAnimationFrame(this.rafId),this.rafId=0),this.audioManager&&this.audioManager.stopAll(),this.input&&this.input.stop(),this.mode==="connected"&&this.networkSync&&this.networkSync.disconnect();let e=this.core.getUser(this.userId);e&&this.options.application.destroyUser&&this.options.application.destroyUser(this.core,e,"Runtime stopped"),this.log("ClientRuntime stopped")}getStats(){let e=this.performanceMonitor.getStats();return{mode:this.mode,running:this.running,userCount:this.core.getUsers().length,fps:e.fps,uptime:this.running?performance.now()-this.startTime:0,totalFrames:0,latency:void 0,lastFrameTiming:e.lastFrame,avgFrameTiming:e.avgFrame}}async destroy(){await this.stop(),this.log("Destroying ClientRuntime"),this.visibilityChangeHandler&&(document.removeEventListener("visibilitychange",this.visibilityChangeHandler),this.visibilityChangeHandler=void 0),this.autoplayOverlay&&(this.autoplayOverlay.destroy(),this.autoplayOverlay=null),this.audioManager&&(this.audioManager.destroy(),this.audioManager=null),this.options.application.destroy&&this.options.application.destroy(),this.rendererManager.destroy(),this.input&&this.input.destroy(),this.networkSync&&this.networkSync.destroy(),this.log("ClientRuntime destroyed")}async createLocalUser(){let e=this.options;this.log(`Creating local user: ${this.userId}`);let t=this.core.createUser(this.userId,e.username);this.audioManager&&t.setAudioProcessor(this.audioManager),this.log("Calling application.initUser()"),this.options.application.initUser(this.core,t,{username:e.username}),t.needsSendSounds()&&(await this.loadSoundsFromRegistry(),t.clearSendSounds()),this.log("Performing initial render"),this.core.endTick(),this.render(),this.log("Initial render complete")}async loadSoundsFromRegistry(){if(!this.audioManager){this.log("Cannot load sounds: AudioManager not initialized");return}let e=this.audioManager.getSoundBank();if(!e){this.log("Cannot load sounds: SoundBank not initialized");return}let n=this.core.getSoundRegistry().getAll();if(n.length===0){this.log("No sounds to load from registry");return}this.log(`Loading ${n.length} sounds from Core registry into AudioManager...`);for(let i of n)try{i.loadType==="file"&&i.data?(await e.loadFromData(i.soundId,i.name,i.data),this.log(`\u{1F50A} Loaded sound: ${i.name} (${i.data.length} bytes)`)):i.loadType==="external"&&i.url&&(await e.loadFromUrl(i.soundId,i.name,i.url),this.log(`\u{1F50A} Loaded external sound: ${i.name} from ${i.url}`))}catch(r){console.error(`[ClientRuntime] Failed to load sound "${i.name}":`,r)}this.log(`\u2713 ${e.size} sounds loaded into AudioManager`)}mainLoop(e){if(!this.running){this.rafId&&(cancelAnimationFrame(this.rafId),this.rafId=0);return}if(this.renderMode==="on-demand"){this.renderRequested&&(this.renderRequested=!1,this.performanceMonitor.updateFPS(e),this.render()),this.rafId=requestAnimationFrame(s=>this.mainLoop(s));return}let t=e-this.lastRenderTimestamp;if(this.lastRenderTimestamp>0&&t<this.FRAME_TIME_MIN-1){this.rafId=requestAnimationFrame(s=>this.mainLoop(s));return}this.lastRenderTimestamp=e;let n=(e-this.lastTimestamp)/1e3,i=Math.min(n,.1);this.lastTimestamp=e,this.performanceMonitor.updateFPS(e);let r=this.core.getUser(this.userId);if(!r){this.rafId=requestAnimationFrame(s=>this.mainLoop(s));return}if(this.mode==="local"){if(this.tickRate===0){let a=performance.now();this.render();let u=performance.now()-a;this.performanceMonitor.recordFrameTiming(0,u),this.rafId=requestAnimationFrame(h=>this.mainLoop(h));return}this.accumulatedTime+=i;let s=1/this.tickRate;this.accumulatedTime>.5&&(this.accumulatedTime=s);let d=0,l=0;for(;this.accumulatedTime>=s&&d<5;){let a=performance.now();this.collectAndApplyInput(r),this.options.application.update(this.core,s),this.options.application.updateUser(this.core,r,s),r.clearTextInputs(),r.hasAudioConfigCommands()&&r.applyAudioConfigCommands(r.flushAudioConfigCommands()),r.hasSoundCommands()&&r.applyAudioCommands(r.flushSoundCommands()),this.core.endTickSplit(),l+=performance.now()-a,this.accumulatedTime-=s,d++}if(d>0){let a=performance.now();this.render();let u=performance.now()-a;this.performanceMonitor.recordFrameTiming(l,u)}}else this.collectAndSendInput();this.rafId=requestAnimationFrame(s=>this.mainLoop(s))}collectAndApplyInput(e){if(!this.input)return;let t=this.rendererManager.getCanvas();if(t){let d=e.getDisplays(),l=d.length>0?d[0].size.x:this.options.width,a=d.length>0?d[0].size.y:this.options.height,h=this.rendererManager.getRenderer()?.getOffsets?.(),g={offsetX:h?.offsetX??0,offsetY:h?.offsetY??0},b=y.InputCollector.collectMousePosition(this.input,t,l,a,g);e.setMousePosition(b.x,b.y,b.over);let m=y.InputCollector.collectTouchPositions(this.input,t,l,a,10,g);this.options.debug&&m.length>0&&console.warn("\u{1F4F1} Collected touches:",m),m.forEach(p=>{e.setTouchPosition(p.id,p.x,p.y,p.over)})}let n=y.InputCollector.collectTextInputs(this.input);n.length>0&&e.setTextInputs(n);let i=e.getInputBindingRegistry(),r=i.getAllAxes(),s=i.getAllButtons();if(r.length>0||s.length>0){let d=y.InputCollector.collectAxisSources(r,this.input),l=y.InputCollector.collectButtonSourcesWithTransitions(s,this.input);r.forEach(a=>{let u=i.evaluateAxis(a.bindingId,d);e.setAxis(a.name,u)}),s.forEach(a=>{let u=new Map,h=new Map,g=new Map;for(let[T,v]of l)u.set(T,v.pressed),h.set(T,v.justPressed),g.set(T,v.justReleased);let b=i.evaluateButton(a.bindingId,u),m=i.evaluateButton(a.bindingId,h),p=i.evaluateButton(a.bindingId,g);e.setButton(a.name,b),e.setButton(`${a.name}_justPressed`,m),e.setButton(`${a.name}_justReleased`,p)})}this.input.poll?.()}collectAndSendInput(){!this.networkSync||!this.input||this.networkSync.sendInput(this.input)}render(){this.rendererManager.render(this.core,this.userId)}onCorePaletteChanged(e){this.log(`Palette changed, updating renderer (${e.size} colors)`);let t=[];for(let i=0;i<256;i++){let r=e.get(i);r?t.push({r:r.r,g:r.g,b:r.b,a:r.a}):t.push({r:0,g:0,b:0,a:0})}let n=this.rendererManager.getRenderer();if(!n){console.warn("[ClientRuntime] Cannot update palette: renderer is null");return}"setPalette"in n&&typeof n.setPalette=="function"?(n.setPalette(t),this.log("\u2713 Renderer palette updated successfully")):console.warn("[ClientRuntime] Renderer does not have setPalette method")}onCoreBitmapFontChanged(e){this.log(`Bitmap font ${e} changed, updating renderer`);let t=this.core.getBitmapFont(e);if(!t){console.warn(`[ClientRuntime] Font ${e} not found in registry`);return}let n=new Map,i=0;for(let s=0;s<256;s++){let d=t.getGlyph(s);d&&(n.set(s,d),i++)}this.log(`Built bitmap font map with ${i} glyphs, dimensions: ${t.getCharWidth()}x${t.getCharHeight()} (cell: ${t.getCellWidth()}x${t.getCellHeight()})`);let r=this.rendererManager.getRenderer();if(!r){console.warn("[ClientRuntime] Cannot update font: renderer is null");return}"setBitmapFont"in r&&typeof r.setBitmapFont=="function"?(r.setBitmapFont(n,t.getCharWidth(),t.getCharHeight(),t.getCellWidth(),t.getCellHeight()),this.log("\u2713 Renderer bitmap font updated successfully")):console.warn("[ClientRuntime] Renderer does not have setBitmapFont method")}getAudioManager(){return this.audioManager}getAudioContext(){return this.audioManager?.getContext()??null}log(e){this.options.debug&&console.warn(`[ClientRuntime/${this.mode}] ${e}`)}};f(L,"ClientRuntime");var A=L;var B=require("@utsp/audio");