@utsp/runtime-client 0.3.0-nightly.20251130202558.253793a → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +1 -1
- package/dist/index.d.ts +34 -0
- package/dist/index.mjs +1 -1
- package/package.json +6 -6
package/dist/index.cjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";var C=Object.defineProperty;var B=Object.getOwnPropertyDescriptor;var O=Object.getOwnPropertyNames;var G=Object.prototype.hasOwnProperty;var H=(u,e,t)=>e in u?C(u,e,{enumerable:!0,configurable:!0,writable:!0,value:t}):u[e]=t;var f=(u,e)=>C(u,"name",{value:e,configurable:!0});var N=(u,e)=>{for(var t in e)C(u,t,{get:e[t],enumerable:!0})},q=(u,e,t,n)=>{if(e&&typeof e=="object"||typeof e=="function")for(let r of O(e))!G.call(u,r)&&r!==t&&C(u,r,{get:()=>e[r],enumerable:!(n=B(e,r))||n.enumerable});return u};var W=u=>q(C({},"__esModule",{value:!0}),u);var s=(u,e,t)=>(H(u,typeof e!="symbol"?e+"":e,t),t);var j={};N(j,{ClientRuntime:()=>k,RendererType:()=>F});module.exports=W(j);var $=require("@utsp/core"),x=require("@utsp/render"),b=require("@utsp/input"),D=require("@utsp/network-client");var F=(t=>(t.TerminalGL="webgl",t.Terminal2D="terminal2d",t))(F||{});var P=class P{constructor(e=60){s(this,"frameCount",0);s(this,"fps",0);s(this,"fpsUpdateTime",0);s(this,"lastCoreTime",0);s(this,"lastRenderTime",0);s(this,"lastTotalTime",0);s(this,"coreTimeSamples",[]);s(this,"renderTimeSamples",[]);s(this,"totalTimeSamples",[]);s(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(P,"PerformanceMonitor");var I=P;var L=require("@utsp/core");var A=class A{constructor(e,t={}){s(this,"renderer");s(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((r,i)=>{let o=f(()=>{if(n++,this.renderer.isReady())this.log(`Renderer ready after ${n*t}ms`),r();else if(n>=e){let l=`Renderer failed to be ready after ${e*t}ms`;this.log(l),i(new Error(l))}else setTimeout(o,t)},"check");o()})}async loadDefaultFont(e){this.log("Loading ASCII 8x8 font");let t=(0,L.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 r=n.displays[0];return this.renderer.renderDisplayData(r),!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(A,"RendererManager");var M=A;var R=require("@utsp/input");var E=class E{constructor(e,t,n,r,i){s(this,"network");s(this,"core");s(this,"rendererManager");s(this,"performanceMonitor");s(this,"options");s(this,"userId","");s(this,"lastReceivedTick",-1);s(this,"connected",!1);this.network=e,this.core=t,this.rendererManager=n,this.performanceMonitor=r,this.options={serverUrl:i.serverUrl,username:i.username,token:i.token??"",debug:i.debug??!1,autoReconnect:i.autoReconnect??!0,canvasWidth:i.canvasWidth,canvasHeight:i.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",r=>{if(r.success){this.userId=r.userId,this.log(`Joined game as ${this.userId}`);let i=this.core.createUser(this.userId,this.options.username);e.initUser(this.core,i,{username:this.options.username,token:this.options.token}),t(this.userId)}else n(new Error(r.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(w=>{t.setTouchPosition(w.id,w.x,w.y,w.over)})}let r=t.getInputBindingRegistry(),i=r.getAllAxes(),o=r.getAllButtons(),d=R.InputCollector.collectAxisSources(i,e),l=R.InputCollector.collectButtonSources(o,e),a=R.InputCollector.collectTextInputs(e);a.length>0&&t.setTextInputs(a);let c={};i.forEach(m=>{let p=r.evaluateAxis(m.bindingId,d);c[m.name]=p,t.setAxis(m.name,p)});let h={};o.forEach(m=>{let p=r.evaluateButton(m.bindingId,l);h[m.name]=p,t.setButton(m.name,p)});let g=t.getMouseDisplayInfo(),y=g?{x:g.localX,y:g.localY}:null;this.network.send("input",{timestamp:Date.now(),axes:c,buttons:h,mousePosition:y,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.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 r=new DataView(n.buffer,n.byteOffset,n.byteLength),i=Number(r.getBigUint64(0,!1));if(t==="update-dynamic"&&i<this.lastReceivedTick)return;if(i>this.lastReceivedTick&&(this.lastReceivedTick=i),this.core.applyUpdatePacketBuffer(this.userId,n)){console.warn(`[CLIENT] ${t}: ${n.length}B (tick ${i}) - 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 ${i})`)}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((r,i)=>{let o=r.getOrders(),d=r.getStatic();(o.length>0||d)&&console.warn(` Layer ${i}: ${o.length} orders, static=${d}, z=${r.getZOrder()}`)})}let t=this.rendererManager.isReady();console.warn(`[CLIENT] Renderer ready: ${t}`)}log(e){this.options.debug&&console.warn(`[NetworkSync] ${e}`)}};f(E,"NetworkSync");var S=E;var U=class U{constructor(e){s(this,"core");s(this,"rendererManager");s(this,"rendererType");s(this,"input",null);s(this,"networkSync",null);s(this,"options");s(this,"running",!1);s(this,"startTime",0);s(this,"lastTimestamp",0);s(this,"userId","");s(this,"mode");s(this,"visibilityChangeHandler");s(this,"tickRate",30);s(this,"accumulatedTime",0);s(this,"FRAME_TIME_MIN",1e3/60);s(this,"lastRenderTimestamp",0);s(this,"rafId",0);s(this,"performanceMonitor");s(this,"renderMode","continuous");s(this,"renderRequested",!1);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},this.userId=e.userId??"local"):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},this.log(`Initializing ClientRuntime (${this.mode} mode)`),this.core=new $.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 M(t,{debug:this.options.debug,useImageDataRendering:this.options.useImageDataRendering}),this.options.inputEnabled?this.input=new b.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 I(60),this.mode==="connected"){let n=this.options,r=new D.SocketIOClient({url:n.serverUrl,autoReconnect:n.autoReconnect,auth:{username:n.username,token:n.token},debug:this.options.debug});this.networkSync=new S(r,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 i=new x.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)"),i.setImageDataRendering(!0)),this.log("Canvas 2D renderer created successfully"),i}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 r=new x.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"),r}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"),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()"),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"),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 i=n[0].getSize(),o=i.x,d=i.y,l=this.rendererManager.getRenderer(),a,c;if(this.rendererType==="webgl"){let g=l.getGridSize();a=g.cols,c=g.rows}else{let h=l;a=h.getCols(),c=h.getRows()}(a!==o||c!==d)&&(this.log(`Adjusting renderer from ${a}\xD7${c} to match display ${o}\xD7${d}`),l.resize(o,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.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.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.log("Calling application.initUser()"),this.options.application.initUser(this.core,t,{username:e.username}),this.log("Performing initial render"),this.core.endTick(),this.render(),this.log("Initial render complete")}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(o=>this.mainLoop(o));return}let t=e-this.lastRenderTimestamp;if(this.lastRenderTimestamp>0&&t<this.FRAME_TIME_MIN-1){this.rafId=requestAnimationFrame(o=>this.mainLoop(o));return}this.lastRenderTimestamp=e;let n=(e-this.lastTimestamp)/1e3,r=Math.min(n,.1);this.lastTimestamp=e,this.performanceMonitor.updateFPS(e);let i=this.core.getUser(this.userId);if(!i){this.rafId=requestAnimationFrame(o=>this.mainLoop(o));return}if(this.mode==="local"){if(this.tickRate===0){let a=performance.now();this.render();let c=performance.now()-a;this.performanceMonitor.recordFrameTiming(0,c),this.rafId=requestAnimationFrame(h=>this.mainLoop(h));return}this.accumulatedTime+=r;let o=1/this.tickRate;this.accumulatedTime>.5&&(this.accumulatedTime=o);let d=0,l=0;for(;this.accumulatedTime>=o&&d<5;){let a=performance.now();this.collectAndApplyInput(i),this.options.application.update(this.core,o),this.options.application.updateUser(this.core,i,o),i.clearTextInputs(),this.core.endTickSplit(),l+=performance.now()-a,this.accumulatedTime-=o,d++}if(d>0){let a=performance.now();this.render();let c=performance.now()-a;this.performanceMonitor.recordFrameTiming(l,c)}}else this.collectAndSendInput();this.rafId=requestAnimationFrame(o=>this.mainLoop(o))}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},y=b.InputCollector.collectMousePosition(this.input,t,l,a,g);e.setMousePosition(y.x,y.y,y.over);let m=b.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=b.InputCollector.collectTextInputs(this.input);n.length>0&&e.setTextInputs(n);let r=e.getInputBindingRegistry(),i=r.getAllAxes(),o=r.getAllButtons();if(i.length>0||o.length>0){let d=b.InputCollector.collectAxisSources(i,this.input),l=b.InputCollector.collectButtonSourcesWithTransitions(o,this.input);i.forEach(a=>{let c=r.evaluateAxis(a.bindingId,d);e.setAxis(a.name,c)}),o.forEach(a=>{let c=new Map,h=new Map,g=new Map;for(let[T,v]of l)c.set(T,v.pressed),h.set(T,v.justPressed),g.set(T,v.justReleased);let y=r.evaluateButton(a.bindingId,c),m=r.evaluateButton(a.bindingId,h),p=r.evaluateButton(a.bindingId,g);e.setButton(a.name,y),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 r=0;r<256;r++){let i=e.get(r);i?t.push({r:i.r,g:i.g,b:i.b,a:i.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,r=0;for(let o=0;o<256;o++){let d=t.getGlyph(o);d&&(n.set(o,d),r++)}this.log(`Built bitmap font map with ${r} glyphs, dimensions: ${t.getCharWidth()}x${t.getCharHeight()} (cell: ${t.getCellWidth()}x${t.getCellHeight()})`);let i=this.rendererManager.getRenderer();if(!i){console.warn("[ClientRuntime] Cannot update font: renderer is null");return}"setBitmapFont"in i&&typeof i.setBitmapFont=="function"?(i.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")}log(e){this.options.debug&&console.warn(`[ClientRuntime/${this.mode}] ${e}`)}};f(U,"ClientRuntime");var k=U;
|
|
1
|
+
"use strict";var C=Object.defineProperty;var D=Object.getOwnPropertyDescriptor;var B=Object.getOwnPropertyNames;var G=Object.prototype.hasOwnProperty;var H=(c,e,t)=>e in c?C(c,e,{enumerable:!0,configurable:!0,writable:!0,value:t}):c[e]=t;var f=(c,e)=>C(c,"name",{value:e,configurable:!0});var N=(c,e)=>{for(var t in e)C(c,t,{get:e[t],enumerable:!0})},q=(c,e,t,n)=>{if(e&&typeof e=="object"||typeof e=="function")for(let r of B(e))!G.call(c,r)&&r!==t&&C(c,r,{get:()=>e[r],enumerable:!(n=D(e,r))||n.enumerable});return c};var W=c=>q(C({},"__esModule",{value:!0}),c);var s=(c,e,t)=>(H(c,typeof e!="symbol"?e+"":e,t),t);var j={};N(j,{ClientRuntime:()=>x,RendererType:()=>F});module.exports=W(j);var L=require("@utsp/core"),w=require("@utsp/render"),y=require("@utsp/input"),$=require("@utsp/network-client");var F=(t=>(t.TerminalGL="webgl",t.Terminal2D="terminal2d",t))(F||{});var A=class A{constructor(e=60){s(this,"frameCount",0);s(this,"fps",0);s(this,"fpsUpdateTime",0);s(this,"lastCoreTime",0);s(this,"lastRenderTime",0);s(this,"lastTotalTime",0);s(this,"coreTimeSamples",[]);s(this,"renderTimeSamples",[]);s(this,"totalTimeSamples",[]);s(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(A,"PerformanceMonitor");var M=A;var O=require("@utsp/core");var P=class P{constructor(e,t={}){s(this,"renderer");s(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((r,i)=>{let o=f(()=>{if(n++,this.renderer.isReady())this.log(`Renderer ready after ${n*t}ms`),r();else if(n>=e){let d=`Renderer failed to be ready after ${e*t}ms`;this.log(d),i(new Error(d))}else setTimeout(o,t)},"check");o()})}async loadDefaultFont(e){this.log("Loading ASCII 8x8 font");let t=(0,O.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 r=n.displays[0];return this.renderer.renderDisplayData(r),!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 E=class E{constructor(e,t,n,r,i){s(this,"network");s(this,"core");s(this,"rendererManager");s(this,"performanceMonitor");s(this,"options");s(this,"userId","");s(this,"lastReceivedTick",-1);s(this,"connected",!1);this.network=e,this.core=t,this.rendererManager=n,this.performanceMonitor=r,this.options={serverUrl:i.serverUrl,username:i.username,token:i.token??"",debug:i.debug??!1,autoReconnect:i.autoReconnect??!0,canvasWidth:i.canvasWidth,canvasHeight:i.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",r=>{if(r.success){this.userId=r.userId,this.log(`Joined game as ${this.userId}`);let i=this.core.createUser(this.userId,this.options.username);e.initUser(this.core,i,{username:this.options.username,token:this.options.token}),t(this.userId)}else n(new Error(r.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 r=t.getInputBindingRegistry(),i=r.getAllAxes(),o=r.getAllButtons(),l=R.InputCollector.collectAxisSources(i,e),d=R.InputCollector.collectButtonSources(o,e),a=R.InputCollector.collectTextInputs(e);a.length>0&&t.setTextInputs(a);let u={};i.forEach(m=>{let p=r.evaluateAxis(m.bindingId,l);u[m.name]=p,t.setAxis(m.name,p)});let h={};o.forEach(m=>{let p=r.evaluateButton(m.bindingId,d);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.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 r=new DataView(n.buffer,n.byteOffset,n.byteLength),i=Number(r.getBigUint64(0,!1));if(t==="update-dynamic"&&i<this.lastReceivedTick)return;if(i>this.lastReceivedTick&&(this.lastReceivedTick=i),this.core.applyUpdatePacketBuffer(this.userId,n)){console.warn(`[CLIENT] ${t}: ${n.length}B (tick ${i}) - APPLIED`),this.options.debug&&this.debugRenderState();let l=performance.now();this.rendererManager.render(this.core,this.userId);let d=performance.now()-l;this.performanceMonitor.recordFrameTiming(0,d),this.options.debug&&console.warn(`[CLIENT] render() completed for ${t} (${d.toFixed(2)}ms)`)}else console.warn(`[CLIENT] Failed to apply ${t} packet (tick ${i})`)}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((r,i)=>{let o=r.getOrders(),l=r.getStatic();(o.length>0||l)&&console.warn(` Layer ${i}: ${o.length} orders, static=${l}, z=${r.getZOrder()}`)})}let t=this.rendererManager.isReady();console.warn(`[CLIENT] Renderer ready: ${t}`)}log(e){this.options.debug&&console.warn(`[NetworkSync] ${e}`)}};f(E,"NetworkSync");var k=E;var U=class U{constructor(e){s(this,"core");s(this,"rendererManager");s(this,"rendererType");s(this,"input",null);s(this,"networkSync",null);s(this,"options");s(this,"running",!1);s(this,"startTime",0);s(this,"lastTimestamp",0);s(this,"userId","");s(this,"mode");s(this,"visibilityChangeHandler");s(this,"tickRate",30);s(this,"accumulatedTime",0);s(this,"FRAME_TIME_MIN",1e3/60);s(this,"lastRenderTimestamp",0);s(this,"rafId",0);s(this,"performanceMonitor");s(this,"renderMode","continuous");s(this,"renderRequested",!1);s(this,"autoplayOverlay",null);s(this,"autoplay",!0);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 L.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,r=new $.SocketIOClient({url:n.serverUrl,autoReconnect:n.autoReconnect,auth:{username:n.username,token:n.token},debug:this.options.debug});this.networkSync=new k(r,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 i=new w.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)"),i.setImageDataRendering(!0)),this.log("Canvas 2D renderer created successfully"),i}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 r=new w.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"),r}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.autoplay||(this.log("Autoplay disabled, showing overlay..."),await new Promise(n=>{this.autoplayOverlay=new w.AutoplayOverlay(this.options.container,{...this.options.autoplayOptions,onStart:()=>{this.log("User clicked start button"),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()"),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"),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 i=n[0].getSize(),o=i.x,l=i.y,d=this.rendererManager.getRenderer(),a,u;if(this.rendererType==="webgl"){let g=d.getGridSize();a=g.cols,u=g.rows}else{let h=d;a=h.getCols(),u=h.getRows()}(a!==o||u!==l)&&(this.log(`Adjusting renderer from ${a}\xD7${u} to match display ${o}\xD7${l}`),d.resize(o,l))}}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.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.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.log("Calling application.initUser()"),this.options.application.initUser(this.core,t,{username:e.username}),this.log("Performing initial render"),this.core.endTick(),this.render(),this.log("Initial render complete")}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(o=>this.mainLoop(o));return}let t=e-this.lastRenderTimestamp;if(this.lastRenderTimestamp>0&&t<this.FRAME_TIME_MIN-1){this.rafId=requestAnimationFrame(o=>this.mainLoop(o));return}this.lastRenderTimestamp=e;let n=(e-this.lastTimestamp)/1e3,r=Math.min(n,.1);this.lastTimestamp=e,this.performanceMonitor.updateFPS(e);let i=this.core.getUser(this.userId);if(!i){this.rafId=requestAnimationFrame(o=>this.mainLoop(o));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+=r;let o=1/this.tickRate;this.accumulatedTime>.5&&(this.accumulatedTime=o);let l=0,d=0;for(;this.accumulatedTime>=o&&l<5;){let a=performance.now();this.collectAndApplyInput(i),this.options.application.update(this.core,o),this.options.application.updateUser(this.core,i,o),i.clearTextInputs(),this.core.endTickSplit(),d+=performance.now()-a,this.accumulatedTime-=o,l++}if(l>0){let a=performance.now();this.render();let u=performance.now()-a;this.performanceMonitor.recordFrameTiming(d,u)}}else this.collectAndSendInput();this.rafId=requestAnimationFrame(o=>this.mainLoop(o))}collectAndApplyInput(e){if(!this.input)return;let t=this.rendererManager.getCanvas();if(t){let l=e.getDisplays(),d=l.length>0?l[0].size.x:this.options.width,a=l.length>0?l[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,d,a,g);e.setMousePosition(b.x,b.y,b.over);let m=y.InputCollector.collectTouchPositions(this.input,t,d,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 r=e.getInputBindingRegistry(),i=r.getAllAxes(),o=r.getAllButtons();if(i.length>0||o.length>0){let l=y.InputCollector.collectAxisSources(i,this.input),d=y.InputCollector.collectButtonSourcesWithTransitions(o,this.input);i.forEach(a=>{let u=r.evaluateAxis(a.bindingId,l);e.setAxis(a.name,u)}),o.forEach(a=>{let u=new Map,h=new Map,g=new Map;for(let[T,v]of d)u.set(T,v.pressed),h.set(T,v.justPressed),g.set(T,v.justReleased);let b=r.evaluateButton(a.bindingId,u),m=r.evaluateButton(a.bindingId,h),p=r.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 r=0;r<256;r++){let i=e.get(r);i?t.push({r:i.r,g:i.g,b:i.b,a:i.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,r=0;for(let o=0;o<256;o++){let l=t.getGlyph(o);l&&(n.set(o,l),r++)}this.log(`Built bitmap font map with ${r} glyphs, dimensions: ${t.getCharWidth()}x${t.getCharHeight()} (cell: ${t.getCellWidth()}x${t.getCellHeight()})`);let i=this.rendererManager.getRenderer();if(!i){console.warn("[ClientRuntime] Cannot update font: renderer is null");return}"setBitmapFont"in i&&typeof i.setBitmapFont=="function"?(i.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")}log(e){this.options.debug&&console.warn(`[ClientRuntime/${this.mode}] ${e}`)}};f(U,"ClientRuntime");var x=U;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { IApplication } from '@utsp/types';
|
|
2
2
|
export { IApplication } from '@utsp/types';
|
|
3
|
+
import { AutoplayOverlayOptions } from '@utsp/render';
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* Runtime Client Types and Options
|
|
@@ -63,6 +64,37 @@ interface BaseClientRuntimeOptions {
|
|
|
63
64
|
captureInput?: boolean;
|
|
64
65
|
/** Enable input handling (keyboard, mouse, touch, gamepad). Default: true. When false, no input listeners are created - useful for display-only clients in documentation pages where you want to scroll freely without the client intercepting events. */
|
|
65
66
|
inputEnabled?: boolean;
|
|
67
|
+
/**
|
|
68
|
+
* Autoplay mode. Default: true.
|
|
69
|
+
*
|
|
70
|
+
* - `true`: Application starts immediately (current behavior)
|
|
71
|
+
* - `false`: Displays a "Click to Start" button overlay. Application only starts after user interaction.
|
|
72
|
+
*
|
|
73
|
+
* Useful for:
|
|
74
|
+
* - Audio APIs that require user interaction before playing
|
|
75
|
+
* - Preventing automatic resource consumption
|
|
76
|
+
* - Better UX on mobile where autoplay may not be desired
|
|
77
|
+
*
|
|
78
|
+
* @example
|
|
79
|
+
* ```typescript
|
|
80
|
+
* const runtime = new ClientRuntime({
|
|
81
|
+
* mode: 'local',
|
|
82
|
+
* application: myApp,
|
|
83
|
+
* container: document.getElementById('app'),
|
|
84
|
+
* autoplay: false, // Show "Click to Start" button
|
|
85
|
+
* autoplayOptions: {
|
|
86
|
+
* buttonText: 'Click to Play',
|
|
87
|
+
* buttonColor: '#00ff00'
|
|
88
|
+
* }
|
|
89
|
+
* });
|
|
90
|
+
* ```
|
|
91
|
+
*/
|
|
92
|
+
autoplay?: boolean;
|
|
93
|
+
/**
|
|
94
|
+
* Autoplay overlay customization options.
|
|
95
|
+
* Only used when `autoplay: false`.
|
|
96
|
+
*/
|
|
97
|
+
autoplayOptions?: Omit<AutoplayOverlayOptions, 'onStart'>;
|
|
66
98
|
}
|
|
67
99
|
/**
|
|
68
100
|
* Local mode options (standalone, no network)
|
|
@@ -156,6 +188,8 @@ declare class ClientRuntime {
|
|
|
156
188
|
private performanceMonitor;
|
|
157
189
|
private renderMode;
|
|
158
190
|
private renderRequested;
|
|
191
|
+
private autoplayOverlay;
|
|
192
|
+
private autoplay;
|
|
159
193
|
constructor(options: ClientRuntimeOptions);
|
|
160
194
|
getMode(): ClientRuntimeMode;
|
|
161
195
|
getRendererType(): RendererType;
|
package/dist/index.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
var A=Object.defineProperty;var U=(f,e,t)=>e in f?A(f,e,{enumerable:!0,configurable:!0,writable:!0,value:t}):f[e]=t;var g=(f,e)=>A(f,"name",{value:e,configurable:!0});var r=(f,e,t)=>(U(f,typeof e!="symbol"?e+"":e,t),t);import{Core as $}from"@utsp/core";import{TerminalGL as D,Terminal2D as B}from"@utsp/render";import{UnifiedInputRouter as O,InputCollector as R}from"@utsp/input";import{SocketIOClient as G}from"@utsp/network-client";var E=(t=>(t.TerminalGL="webgl",t.Terminal2D="terminal2d",t))(E||{});var S=class S{constructor(e=60){r(this,"frameCount",0);r(this,"fps",0);r(this,"fpsUpdateTime",0);r(this,"lastCoreTime",0);r(this,"lastRenderTime",0);r(this,"lastTotalTime",0);r(this,"coreTimeSamples",[]);r(this,"renderTimeSamples",[]);r(this,"totalTimeSamples",[]);r(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}};g(S,"PerformanceMonitor");var w=S;import{createASCII8x8FontLoad as L}from"@utsp/core";var k=class k{constructor(e,t={}){r(this,"renderer");r(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((s,i)=>{let o=g(()=>{if(n++,this.renderer.isReady())this.log(`Renderer ready after ${n*t}ms`),s();else if(n>=e){let l=`Renderer failed to be ready after ${e*t}ms`;this.log(l),i(new Error(l))}else setTimeout(o,t)},"check");o()})}async loadDefaultFont(e){this.log("Loading ASCII 8x8 font");let t=L(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 s=n.displays[0];return this.renderer.renderDisplayData(s),!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}`)}};g(k,"RendererManager");var I=k;import{InputCollector as T}from"@utsp/input";var x=class x{constructor(e,t,n,s,i){r(this,"network");r(this,"core");r(this,"rendererManager");r(this,"performanceMonitor");r(this,"options");r(this,"userId","");r(this,"lastReceivedTick",-1);r(this,"connected",!1);this.network=e,this.core=t,this.rendererManager=n,this.performanceMonitor=s,this.options={serverUrl:i.serverUrl,username:i.username,token:i.token??"",debug:i.debug??!1,autoReconnect:i.autoReconnect??!0,canvasWidth:i.canvasWidth,canvasHeight:i.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",s=>{if(s.success){this.userId=s.userId,this.log(`Joined game as ${this.userId}`);let i=this.core.createUser(this.userId,this.options.username);e.initUser(this.core,i,{username:this.options.username,token:this.options.token}),t(this.userId)}else n(new Error(s.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 u=this.rendererManager.getRenderer()?.getOffsets?.(),v={offsetX:u?.offsetX??0,offsetY:u?.offsetY??0},y=T.collectMousePosition(e,n,this.options.canvasWidth,this.options.canvasHeight,v);t.setMousePosition(y.x,y.y,y.over),T.collectTouchPositions(e,n,this.options.canvasWidth,this.options.canvasHeight,10,v).forEach(C=>{t.setTouchPosition(C.id,C.x,C.y,C.over)})}let s=t.getInputBindingRegistry(),i=s.getAllAxes(),o=s.getAllButtons(),d=T.collectAxisSources(i,e),l=T.collectButtonSources(o,e),a=T.collectTextInputs(e);a.length>0&&t.setTextInputs(a);let c={};i.forEach(h=>{let u=s.evaluateAxis(h.bindingId,d);c[h.name]=u,t.setAxis(h.name,u)});let p={};o.forEach(h=>{let u=s.evaluateButton(h.bindingId,l);p[h.name]=u,t.setButton(h.name,u)});let m=t.getMouseDisplayInfo(),b=m?{x:m.localX,y:m.localY}:null;this.network.send("input",{timestamp:Date.now(),axes:c,buttons:p,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.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 s=new DataView(n.buffer,n.byteOffset,n.byteLength),i=Number(s.getBigUint64(0,!1));if(t==="update-dynamic"&&i<this.lastReceivedTick)return;if(i>this.lastReceivedTick&&(this.lastReceivedTick=i),this.core.applyUpdatePacketBuffer(this.userId,n)){console.warn(`[CLIENT] ${t}: ${n.length}B (tick ${i}) - 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 ${i})`)}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((s,i)=>{let o=s.getOrders(),d=s.getStatic();(o.length>0||d)&&console.warn(` Layer ${i}: ${o.length} orders, static=${d}, z=${s.getZOrder()}`)})}let t=this.rendererManager.isReady();console.warn(`[CLIENT] Renderer ready: ${t}`)}log(e){this.options.debug&&console.warn(`[NetworkSync] ${e}`)}};g(x,"NetworkSync");var M=x;var P=class P{constructor(e){r(this,"core");r(this,"rendererManager");r(this,"rendererType");r(this,"input",null);r(this,"networkSync",null);r(this,"options");r(this,"running",!1);r(this,"startTime",0);r(this,"lastTimestamp",0);r(this,"userId","");r(this,"mode");r(this,"visibilityChangeHandler");r(this,"tickRate",30);r(this,"accumulatedTime",0);r(this,"FRAME_TIME_MIN",1e3/60);r(this,"lastRenderTimestamp",0);r(this,"rafId",0);r(this,"performanceMonitor");r(this,"renderMode","continuous");r(this,"renderRequested",!1);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},this.userId=e.userId??"local"):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},this.log(`Initializing ClientRuntime (${this.mode} mode)`),this.core=new $({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 I(t,{debug:this.options.debug,useImageDataRendering:this.options.useImageDataRendering}),this.options.inputEnabled?this.input=new O({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 w(60),this.mode==="connected"){let n=this.options,s=new G({url:n.serverUrl,autoReconnect:n.autoReconnect,auth:{username:n.username,token:n.token},debug:this.options.debug});this.networkSync=new M(s,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 i=new B(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)"),i.setImageDataRendering(!0)),this.log("Canvas 2D renderer created successfully"),i}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 s=new D(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"),s}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"),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()"),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"),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 i=n[0].getSize(),o=i.x,d=i.y,l=this.rendererManager.getRenderer(),a,c;if(this.rendererType==="webgl"){let m=l.getGridSize();a=m.cols,c=m.rows}else{let p=l;a=p.getCols(),c=p.getRows()}(a!==o||c!==d)&&(this.log(`Adjusting renderer from ${a}\xD7${c} to match display ${o}\xD7${d}`),l.resize(o,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.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.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.log("Calling application.initUser()"),this.options.application.initUser(this.core,t,{username:e.username}),this.log("Performing initial render"),this.core.endTick(),this.render(),this.log("Initial render complete")}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(o=>this.mainLoop(o));return}let t=e-this.lastRenderTimestamp;if(this.lastRenderTimestamp>0&&t<this.FRAME_TIME_MIN-1){this.rafId=requestAnimationFrame(o=>this.mainLoop(o));return}this.lastRenderTimestamp=e;let n=(e-this.lastTimestamp)/1e3,s=Math.min(n,.1);this.lastTimestamp=e,this.performanceMonitor.updateFPS(e);let i=this.core.getUser(this.userId);if(!i){this.rafId=requestAnimationFrame(o=>this.mainLoop(o));return}if(this.mode==="local"){if(this.tickRate===0){let a=performance.now();this.render();let c=performance.now()-a;this.performanceMonitor.recordFrameTiming(0,c),this.rafId=requestAnimationFrame(p=>this.mainLoop(p));return}this.accumulatedTime+=s;let o=1/this.tickRate;this.accumulatedTime>.5&&(this.accumulatedTime=o);let d=0,l=0;for(;this.accumulatedTime>=o&&d<5;){let a=performance.now();this.collectAndApplyInput(i),this.options.application.update(this.core,o),this.options.application.updateUser(this.core,i,o),i.clearTextInputs(),this.core.endTickSplit(),l+=performance.now()-a,this.accumulatedTime-=o,d++}if(d>0){let a=performance.now();this.render();let c=performance.now()-a;this.performanceMonitor.recordFrameTiming(l,c)}}else this.collectAndSendInput();this.rafId=requestAnimationFrame(o=>this.mainLoop(o))}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,p=this.rendererManager.getRenderer()?.getOffsets?.(),m={offsetX:p?.offsetX??0,offsetY:p?.offsetY??0},b=R.collectMousePosition(this.input,t,l,a,m);e.setMousePosition(b.x,b.y,b.over);let h=R.collectTouchPositions(this.input,t,l,a,10,m);this.options.debug&&h.length>0&&console.warn("\u{1F4F1} Collected touches:",h),h.forEach(u=>{e.setTouchPosition(u.id,u.x,u.y,u.over)})}let n=R.collectTextInputs(this.input);n.length>0&&e.setTextInputs(n);let s=e.getInputBindingRegistry(),i=s.getAllAxes(),o=s.getAllButtons();if(i.length>0||o.length>0){let d=R.collectAxisSources(i,this.input),l=R.collectButtonSourcesWithTransitions(o,this.input);i.forEach(a=>{let c=s.evaluateAxis(a.bindingId,d);e.setAxis(a.name,c)}),o.forEach(a=>{let c=new Map,p=new Map,m=new Map;for(let[v,y]of l)c.set(v,y.pressed),p.set(v,y.justPressed),m.set(v,y.justReleased);let b=s.evaluateButton(a.bindingId,c),h=s.evaluateButton(a.bindingId,p),u=s.evaluateButton(a.bindingId,m);e.setButton(a.name,b),e.setButton(`${a.name}_justPressed`,h),e.setButton(`${a.name}_justReleased`,u)})}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 s=0;s<256;s++){let i=e.get(s);i?t.push({r:i.r,g:i.g,b:i.b,a:i.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,s=0;for(let o=0;o<256;o++){let d=t.getGlyph(o);d&&(n.set(o,d),s++)}this.log(`Built bitmap font map with ${s} glyphs, dimensions: ${t.getCharWidth()}x${t.getCharHeight()} (cell: ${t.getCellWidth()}x${t.getCellHeight()})`);let i=this.rendererManager.getRenderer();if(!i){console.warn("[ClientRuntime] Cannot update font: renderer is null");return}"setBitmapFont"in i&&typeof i.setBitmapFont=="function"?(i.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")}log(e){this.options.debug&&console.warn(`[ClientRuntime/${this.mode}] ${e}`)}};g(P,"ClientRuntime");var F=P;export{F as ClientRuntime,E as RendererType};
|
|
1
|
+
var P=Object.defineProperty;var U=(f,e,t)=>e in f?P(f,e,{enumerable:!0,configurable:!0,writable:!0,value:t}):f[e]=t;var g=(f,e)=>P(f,"name",{value:e,configurable:!0});var r=(f,e,t)=>(U(f,typeof e!="symbol"?e+"":e,t),t);import{Core as L}from"@utsp/core";import{TerminalGL as $,Terminal2D as D,AutoplayOverlay as B}from"@utsp/render";import{UnifiedInputRouter as G,InputCollector as R}from"@utsp/input";import{SocketIOClient as H}from"@utsp/network-client";var E=(t=>(t.TerminalGL="webgl",t.Terminal2D="terminal2d",t))(E||{});var S=class S{constructor(e=60){r(this,"frameCount",0);r(this,"fps",0);r(this,"fpsUpdateTime",0);r(this,"lastCoreTime",0);r(this,"lastRenderTime",0);r(this,"lastTotalTime",0);r(this,"coreTimeSamples",[]);r(this,"renderTimeSamples",[]);r(this,"totalTimeSamples",[]);r(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}};g(S,"PerformanceMonitor");var C=S;import{createASCII8x8FontLoad as O}from"@utsp/core";var k=class k{constructor(e,t={}){r(this,"renderer");r(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((s,i)=>{let o=g(()=>{if(n++,this.renderer.isReady())this.log(`Renderer ready after ${n*t}ms`),s();else if(n>=e){let d=`Renderer failed to be ready after ${e*t}ms`;this.log(d),i(new Error(d))}else setTimeout(o,t)},"check");o()})}async loadDefaultFont(e){this.log("Loading ASCII 8x8 font");let t=O(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 s=n.displays[0];return this.renderer.renderDisplayData(s),!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}`)}};g(k,"RendererManager");var I=k;import{InputCollector as T}from"@utsp/input";var x=class x{constructor(e,t,n,s,i){r(this,"network");r(this,"core");r(this,"rendererManager");r(this,"performanceMonitor");r(this,"options");r(this,"userId","");r(this,"lastReceivedTick",-1);r(this,"connected",!1);this.network=e,this.core=t,this.rendererManager=n,this.performanceMonitor=s,this.options={serverUrl:i.serverUrl,username:i.username,token:i.token??"",debug:i.debug??!1,autoReconnect:i.autoReconnect??!0,canvasWidth:i.canvasWidth,canvasHeight:i.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",s=>{if(s.success){this.userId=s.userId,this.log(`Joined game as ${this.userId}`);let i=this.core.createUser(this.userId,this.options.username);e.initUser(this.core,i,{username:this.options.username,token:this.options.token}),t(this.userId)}else n(new Error(s.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 c=this.rendererManager.getRenderer()?.getOffsets?.(),v={offsetX:c?.offsetX??0,offsetY:c?.offsetY??0},b=T.collectMousePosition(e,n,this.options.canvasWidth,this.options.canvasHeight,v);t.setMousePosition(b.x,b.y,b.over),T.collectTouchPositions(e,n,this.options.canvasWidth,this.options.canvasHeight,10,v).forEach(w=>{t.setTouchPosition(w.id,w.x,w.y,w.over)})}let s=t.getInputBindingRegistry(),i=s.getAllAxes(),o=s.getAllButtons(),l=T.collectAxisSources(i,e),d=T.collectButtonSources(o,e),a=T.collectTextInputs(e);a.length>0&&t.setTextInputs(a);let u={};i.forEach(h=>{let c=s.evaluateAxis(h.bindingId,l);u[h.name]=c,t.setAxis(h.name,c)});let p={};o.forEach(h=>{let c=s.evaluateButton(h.bindingId,d);p[h.name]=c,t.setButton(h.name,c)});let m=t.getMouseDisplayInfo(),y=m?{x:m.localX,y:m.localY}:null;this.network.send("input",{timestamp:Date.now(),axes:u,buttons:p,mousePosition:y,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.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 s=new DataView(n.buffer,n.byteOffset,n.byteLength),i=Number(s.getBigUint64(0,!1));if(t==="update-dynamic"&&i<this.lastReceivedTick)return;if(i>this.lastReceivedTick&&(this.lastReceivedTick=i),this.core.applyUpdatePacketBuffer(this.userId,n)){console.warn(`[CLIENT] ${t}: ${n.length}B (tick ${i}) - APPLIED`),this.options.debug&&this.debugRenderState();let l=performance.now();this.rendererManager.render(this.core,this.userId);let d=performance.now()-l;this.performanceMonitor.recordFrameTiming(0,d),this.options.debug&&console.warn(`[CLIENT] render() completed for ${t} (${d.toFixed(2)}ms)`)}else console.warn(`[CLIENT] Failed to apply ${t} packet (tick ${i})`)}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((s,i)=>{let o=s.getOrders(),l=s.getStatic();(o.length>0||l)&&console.warn(` Layer ${i}: ${o.length} orders, static=${l}, z=${s.getZOrder()}`)})}let t=this.rendererManager.isReady();console.warn(`[CLIENT] Renderer ready: ${t}`)}log(e){this.options.debug&&console.warn(`[NetworkSync] ${e}`)}};g(x,"NetworkSync");var M=x;var A=class A{constructor(e){r(this,"core");r(this,"rendererManager");r(this,"rendererType");r(this,"input",null);r(this,"networkSync",null);r(this,"options");r(this,"running",!1);r(this,"startTime",0);r(this,"lastTimestamp",0);r(this,"userId","");r(this,"mode");r(this,"visibilityChangeHandler");r(this,"tickRate",30);r(this,"accumulatedTime",0);r(this,"FRAME_TIME_MIN",1e3/60);r(this,"lastRenderTimestamp",0);r(this,"rafId",0);r(this,"performanceMonitor");r(this,"renderMode","continuous");r(this,"renderRequested",!1);r(this,"autoplayOverlay",null);r(this,"autoplay",!0);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 L({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 I(t,{debug:this.options.debug,useImageDataRendering:this.options.useImageDataRendering}),this.options.inputEnabled?this.input=new G({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 C(60),this.mode==="connected"){let n=this.options,s=new H({url:n.serverUrl,autoReconnect:n.autoReconnect,auth:{username:n.username,token:n.token},debug:this.options.debug});this.networkSync=new M(s,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 i=new D(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)"),i.setImageDataRendering(!0)),this.log("Canvas 2D renderer created successfully"),i}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 s=new $(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"),s}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.autoplay||(this.log("Autoplay disabled, showing overlay..."),await new Promise(n=>{this.autoplayOverlay=new B(this.options.container,{...this.options.autoplayOptions,onStart:()=>{this.log("User clicked start button"),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()"),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"),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 i=n[0].getSize(),o=i.x,l=i.y,d=this.rendererManager.getRenderer(),a,u;if(this.rendererType==="webgl"){let m=d.getGridSize();a=m.cols,u=m.rows}else{let p=d;a=p.getCols(),u=p.getRows()}(a!==o||u!==l)&&(this.log(`Adjusting renderer from ${a}\xD7${u} to match display ${o}\xD7${l}`),d.resize(o,l))}}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.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.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.log("Calling application.initUser()"),this.options.application.initUser(this.core,t,{username:e.username}),this.log("Performing initial render"),this.core.endTick(),this.render(),this.log("Initial render complete")}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(o=>this.mainLoop(o));return}let t=e-this.lastRenderTimestamp;if(this.lastRenderTimestamp>0&&t<this.FRAME_TIME_MIN-1){this.rafId=requestAnimationFrame(o=>this.mainLoop(o));return}this.lastRenderTimestamp=e;let n=(e-this.lastTimestamp)/1e3,s=Math.min(n,.1);this.lastTimestamp=e,this.performanceMonitor.updateFPS(e);let i=this.core.getUser(this.userId);if(!i){this.rafId=requestAnimationFrame(o=>this.mainLoop(o));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(p=>this.mainLoop(p));return}this.accumulatedTime+=s;let o=1/this.tickRate;this.accumulatedTime>.5&&(this.accumulatedTime=o);let l=0,d=0;for(;this.accumulatedTime>=o&&l<5;){let a=performance.now();this.collectAndApplyInput(i),this.options.application.update(this.core,o),this.options.application.updateUser(this.core,i,o),i.clearTextInputs(),this.core.endTickSplit(),d+=performance.now()-a,this.accumulatedTime-=o,l++}if(l>0){let a=performance.now();this.render();let u=performance.now()-a;this.performanceMonitor.recordFrameTiming(d,u)}}else this.collectAndSendInput();this.rafId=requestAnimationFrame(o=>this.mainLoop(o))}collectAndApplyInput(e){if(!this.input)return;let t=this.rendererManager.getCanvas();if(t){let l=e.getDisplays(),d=l.length>0?l[0].size.x:this.options.width,a=l.length>0?l[0].size.y:this.options.height,p=this.rendererManager.getRenderer()?.getOffsets?.(),m={offsetX:p?.offsetX??0,offsetY:p?.offsetY??0},y=R.collectMousePosition(this.input,t,d,a,m);e.setMousePosition(y.x,y.y,y.over);let h=R.collectTouchPositions(this.input,t,d,a,10,m);this.options.debug&&h.length>0&&console.warn("\u{1F4F1} Collected touches:",h),h.forEach(c=>{e.setTouchPosition(c.id,c.x,c.y,c.over)})}let n=R.collectTextInputs(this.input);n.length>0&&e.setTextInputs(n);let s=e.getInputBindingRegistry(),i=s.getAllAxes(),o=s.getAllButtons();if(i.length>0||o.length>0){let l=R.collectAxisSources(i,this.input),d=R.collectButtonSourcesWithTransitions(o,this.input);i.forEach(a=>{let u=s.evaluateAxis(a.bindingId,l);e.setAxis(a.name,u)}),o.forEach(a=>{let u=new Map,p=new Map,m=new Map;for(let[v,b]of d)u.set(v,b.pressed),p.set(v,b.justPressed),m.set(v,b.justReleased);let y=s.evaluateButton(a.bindingId,u),h=s.evaluateButton(a.bindingId,p),c=s.evaluateButton(a.bindingId,m);e.setButton(a.name,y),e.setButton(`${a.name}_justPressed`,h),e.setButton(`${a.name}_justReleased`,c)})}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 s=0;s<256;s++){let i=e.get(s);i?t.push({r:i.r,g:i.g,b:i.b,a:i.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,s=0;for(let o=0;o<256;o++){let l=t.getGlyph(o);l&&(n.set(o,l),s++)}this.log(`Built bitmap font map with ${s} glyphs, dimensions: ${t.getCharWidth()}x${t.getCharHeight()} (cell: ${t.getCellWidth()}x${t.getCellHeight()})`);let i=this.rendererManager.getRenderer();if(!i){console.warn("[ClientRuntime] Cannot update font: renderer is null");return}"setBitmapFont"in i&&typeof i.setBitmapFont=="function"?(i.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")}log(e){this.options.debug&&console.warn(`[ClientRuntime/${this.mode}] ${e}`)}};g(A,"ClientRuntime");var F=A;export{F as ClientRuntime,E as RendererType};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@utsp/runtime-client",
|
|
3
|
-
"version": "0.3.0
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "UTSP Runtime Client - Local and multi-user client runtime",
|
|
5
5
|
"author": "THP Software",
|
|
6
6
|
"license": "MIT",
|
|
@@ -48,11 +48,11 @@
|
|
|
48
48
|
"access": "public"
|
|
49
49
|
},
|
|
50
50
|
"dependencies": {
|
|
51
|
-
"@utsp/core": "0.3.0
|
|
52
|
-
"@utsp/input": "0.3.0
|
|
53
|
-
"@utsp/network-client": "0.3.0
|
|
54
|
-
"@utsp/
|
|
55
|
-
"@utsp/
|
|
51
|
+
"@utsp/core": "0.3.0",
|
|
52
|
+
"@utsp/input": "0.3.0",
|
|
53
|
+
"@utsp/network-client": "0.3.0",
|
|
54
|
+
"@utsp/types": "0.3.0",
|
|
55
|
+
"@utsp/render": "0.3.0"
|
|
56
56
|
},
|
|
57
57
|
"devDependencies": {
|
|
58
58
|
"typescript": "^5.6.3"
|