@utsp/runtime-server 0.16.0-nightly.20251231153713.b870da1 → 0.16.0-nightly.20260110151828.b70d960

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.
@@ -4,7 +4,7 @@
4
4
  <meta charset="UTF-8" />
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
6
  <title>UTSP Visual Debugger</title>
7
- <script type="module" crossorigin src="/assets/index-B7ydytdk.js"></script>
7
+ <script type="module" crossorigin src="/assets/index-CeQzan2M.js"></script>
8
8
  <link rel="stylesheet" crossorigin href="/assets/index-DuratEin.css">
9
9
  </head>
10
10
  <body>
package/dist/index.cjs CHANGED
@@ -1,4 +1,4 @@
1
- "use strict";var C=Object.create;var d=Object.defineProperty;var A=Object.getOwnPropertyDescriptor;var T=Object.getOwnPropertyNames;var z=Object.getPrototypeOf,M=Object.prototype.hasOwnProperty;var $=(c,t,e)=>t in c?d(c,t,{enumerable:!0,configurable:!0,writable:!0,value:e}):c[t]=e;var k=(c,t)=>d(c,"name",{value:t,configurable:!0});var x=(c,t)=>{for(var e in t)d(c,e,{get:t[e],enumerable:!0})},b=(c,t,e,s)=>{if(t&&typeof t=="object"||typeof t=="function")for(let i of T(t))!M.call(c,i)&&i!==e&&d(c,i,{get:()=>t[i],enumerable:!(s=A(t,i))||s.enumerable});return c};var B=(c,t,e)=>(e=c!=null?C(z(c)):{},b(t||!c||!c.__esModule?d(e,"default",{value:c,enumerable:!0}):e,c)),R=c=>b(d({},"__esModule",{value:!0}),c);var h=(c,t,e)=>($(c,typeof t!="symbol"?t+"":t,e),e);var I={};x(I,{ServerRuntime:()=>y});module.exports=R(I);var v=require("@utsp/core"),w=require("@utsp/network-server"),g=require("@utsp/types"),S=require("fs/promises"),m=B(require("path"),1),U=require("url"),P={};var u=class u{constructor(t){h(this,"core");h(this,"network");h(this,"options");h(this,"running",!1);h(this,"startTime",0);h(this,"lastTimestamp",0);h(this,"tickCount",0);h(this,"tps",0);h(this,"tpsUpdateTime",0);h(this,"tickInterval",null);h(this,"totalBytesSent",0);h(this,"debugClients",new Set);h(this,"debugDisplayOverrides",new Map);this.validateApplication(t.application),this.options={application:t.application,port:t.port,host:t.host??"0.0.0.0",tickRate:t.tickRate??20,maxConnections:t.maxConnections??100,debug:t.debug??!1,debugUi:t.debugUi??!1,debugUiServePath:t.debugUiServePath??this.resolveDefaultDebugUiServePath(),width:t.width??80,height:t.height??25,cors:t.cors??{origin:"*"}},this.log("Initializing ServerRuntime..."),this.core=new v.Core({mode:"server",maxUsers:this.options.maxConnections}),this.network=new w.SocketIOServer({port:this.options.port,host:this.options.host,cors:this.options.cors,maxConnections:this.options.maxConnections,debug:this.options.debug}),this.log("ServerRuntime initialized")}getMode(){return"server"}isRunning(){return this.running}setTickRate(t){if(t<=0||t>1e3)throw new Error(`Invalid tick rate: ${t}. Must be between 1 and 1000.`);if(this.options.tickRate=t,this.log(`Tick rate changed to ${t} TPS`),this.core.getUsers().forEach(e=>{e.setBytesTickRate(t)}),this.running&&this.tickInterval){clearInterval(this.tickInterval);let e=1e3/t;this.log(`Restarting tick loop at ${t} TPS (${e}ms)...`),this.tickInterval=setInterval(()=>{this.tick()},e)}}getTickRate(){return this.options.tickRate}async start(){if(this.running){this.log("Already running");return}this.log("Starting ServerRuntime..."),await this.network.start(),this.log(`Server listening on ${this.options.host}:${this.options.port}`),this.setupDebugUiStaticHosting(),this.setupNetworkHandlers(),this.log("Calling application.init()..."),await this.options.application.init(this.core,this),this.running=!0,this.startTime=Date.now(),this.lastTimestamp=this.startTime,this.tpsUpdateTime=this.startTime;let t=1e3/this.options.tickRate;this.log(`Starting tick loop at ${this.options.tickRate} TPS (${t}ms)...`),this.tickInterval=setInterval(()=>{this.tick()},t)}async stop(){if(!this.running)return;this.log("Stopping ServerRuntime..."),this.running=!1,this.tickInterval&&(clearInterval(this.tickInterval),this.tickInterval=null),this.network.getClients().forEach(e=>{this.network.disconnectClient(e,"Server stopped")}),await this.network.stop(),this.log("ServerRuntime stopped")}getStats(){return{mode:"server",running:this.running,userCount:this.core.getUsers().length,tps:this.tps,uptime:this.running?Date.now()-this.startTime:0,totalTicks:this.tickCount,totalBytesSent:this.totalBytesSent}}async destroy(){await this.stop(),this.log("Destroying ServerRuntime..."),this.options.application.destroy&&this.options.application.destroy(),await this.network.destroy(),this.log("ServerRuntime destroyed")}setupNetworkHandlers(){this.network.onConnect(t=>{this.log(`Client connected: ${t}`)}),this.network.onDisconnect((t,e)=>{this.log(`Client disconnected: ${t} (${e})`),this.debugClients.delete(t),this.debugDisplayOverrides.delete(t);let s=this.core.getUser(t);s&&(this.options.application.destroyUser&&this.options.application.destroyUser(this.core,s,e),this.core.removeUser(t))}),this.network.on("join",(t,e)=>{this.log(`Join request from ${t}: ${e.username}`);try{let s=e.username||`Player${t.substring(0,4)}`,i=this.core.createUser(t,s);i.setBytesTickRate(this.options.tickRate),this.options.application.initUser(this.core,i,{username:s,token:e.token}),this.options.debugUi&&this.syncDebugDisplay(i),this.sendAndCount(t,"join_response",{success:!0,userId:t,roomId:"main"});let n=this.core.generateAllLoadPackets();this.log(`Sending ${n.length} load packets to ${t}...`),n.forEach(p=>{this.sendAndCount(t,"load",p)});let o=this.core.generateMacroLoadPackets(t);o.length>0&&(this.log(`Sending ${o.length} macro load packets to ${t}...`),o.forEach(p=>{this.sendAndCount(t,"load",p)}));let a=i.getInputBindingsLoadPacket();a&&(this.sendAndCount(t,"input-bindings",a),this.log(`Sent input bindings to ${t}`));let r=this.core.generateInitialUpdatePacket(t);r&&(this.sendAndCount(t,"update",r),this.log(`Sent initial update packet to ${t}`)),this.log(`User ${s} (${t}) joined`)}catch(s){this.log(`Failed to join: ${s}`),this.sendAndCount(t,"join_response",{success:!1,error:"Failed to join game"})}}),this.network.on("leave",t=>{this.log(`Leave request from ${t}`),this.network.disconnectClient(t,"User left")}),this.network.on("input",(t,e)=>{let s=this.core.getUser(t);if(!s){this.log(`Input from unknown user: ${t}`);return}let i=e instanceof Uint8Array?e:new Uint8Array(e);s.decodeAndApplyCompressedInput(i),this.syncResponsiveDisplaySize(s)}),this.network.on("audio-ack",(t,e)=>{let s=this.core.getUser(t);if(!s){this.log(`Audio ACK from unknown user: ${t}`);return}s.handleAudioAck(e),this.options.application.onAudioAck&&this.options.application.onAudioAck(this.core,s,e),this.log(`Audio ACK from ${t}: ${e.type}`)}),this.network.onBridge("*",(t,e)=>{let s=this.core.getUser(t);if(!s){this.log(`Bridge message from unknown user: ${t}`);return}if(e.channel==="__debug_auth"){this.options.debugUi&&(this.debugClients.add(t),this.log(`Debug client authenticated: ${t}`));return}if(e.channel==="__debug_display_255"){this.handleDebugDisplayBridge(s,e.data);return}e.channel.startsWith("__debug_")||(this.options.application.onBridgeMessage&&this.options.application.onBridgeMessage(this.core,s,e.channel,e.data),this.log(`Bridge from ${t} on channel '${e.channel}'`))})}tick(){if(!this.running)return;let t=Date.now(),e=(t-this.lastTimestamp)/1e3;this.lastTimestamp=t,this.tickCount++,t-this.tpsUpdateTime>=1e3&&(this.tps=Math.round(this.tickCount*1e3/(t-this.tpsUpdateTime)),this.tickCount=0,this.tpsUpdateTime=t),this.options.application.update&&this.options.application.update(this.core,e),this.core.getUsers().forEach(i=>{if(this.options.application.updateUser(this.core,i,e),i.clearTextInputs(),this.options.debugUi&&this.syncDebugDisplay(i),i.needsSendSounds()){let n=this.core.generateSoundLoadPackets();n.length>0&&(this.log(`Sending ${n.length} sound packets to ${i.id}...`),n.forEach(o=>{this.sendAndCount(i.id,"sound-load",o)})),i.clearSendSounds()}}),this.broadcastUpdates()}broadcastUpdates(){let t=this.core.endTick(),e=new Map;if(t.forEach(({static:s,dynamic:i},n)=>{s&&(this.sendAndCount(n,"update-static",s),e.set(n,{staticSize:s.length,dynamicSize:e.get(n)?.dynamicSize??0})),i&&(this.sendVolatileAndCount(n,"update-dynamic",i),e.set(n,{staticSize:e.get(n)?.staticSize??0,dynamicSize:i.length}));let o=this.core.getUser(n);o&&o.hasBridgeMessages()&&o.getBridgeMessages().forEach(r=>{this.network.sendBridge(n,r.channel,r.data)})}),this.options.debugUi&&this.debugClients.size>0){let s=this.core.getUsers().map(n=>{let o=e.get(n.id)??{staticSize:0,dynamicSize:0},a=this.core.getStats().displayCompositeLayerIds,r={};for(let[p,l]of a.entries())r[p]={layerIdsTopToBottom:l};return{...n.getDebugInfo(),displayComposite:r,packets:o}}),i={tick:{tickRate:this.options.tickRate,tickCount:this.tickCount,tps:this.tps,uptime:this.running?Date.now()-this.startTime:0},users:s,totals:{totalBytesSent:this.totalBytesSent}};this.debugClients.forEach(n=>{this.network.sendBridge(n,"__debug_meta",i)})}}sendAndCount(t,e,s){this.network.sendToClient(t,e,s);let i=this.getDataSize(s);this.totalBytesSent+=i;let n=this.core.getUser(t);n&&n.recordBytesSent(i)}sendVolatileAndCount(t,e,s){let i=this.network;i.sendToClientVolatile?i.sendToClientVolatile(t,e,s):this.network.sendToClient(t,e,s),this.totalBytesSent+=s.length;let n=this.core.getUser(t);n&&n.recordBytesSent(s.length)}getDataSize(t){return t instanceof Uint8Array||typeof t=="string"?t.length:JSON.stringify(t).length}syncResponsiveDisplaySize(t){let e=t.getDisplays();if(e.length!==0)for(let s of e){let i=s.getId();if(t.getScalingMode(i)!=="responsive")continue;let o=t.getDisplayViewport(i);if(!o)continue;let a=t.getCellSize(i);if(a.cellWidth<=0||a.cellHeight<=0)continue;let r=Math.floor(o.pixelWidth/a.cellWidth),p=Math.floor(o.pixelHeight/a.cellHeight);r=Math.min(256,Math.max(1,r)),p=Math.min(256,Math.max(1,p));let l=s.getSize();(l.x!==r||l.y!==p)&&s.setSize(new g.Vector2(r,p))}}handleDebugDisplayBridge(t,e){if(!this.options.debugUi)return;let s=e&&typeof e=="object"?e:null;if(!s)return;let n={...this.debugDisplayOverrides.get(t.id)??{}},o=!1,a=s.origin;a&&typeof a.x=="number"&&typeof a.y=="number"&&(n.origin=new g.Vector2(a.x,a.y),o=!0);let r=s.size;if(r&&typeof r.x=="number"&&typeof r.y=="number"){let p=Math.min(256,Math.max(1,Math.round(r.x))),l=Math.min(256,Math.max(1,Math.round(r.y)));n.size=new g.Vector2(p,l),o=!0}o&&(this.debugDisplayOverrides.set(t.id,n),this.syncDebugDisplay(t))}syncDebugDisplay(t){if(!this.options.debugUi)return;let e=this.ensureDebugDisplay(t),s=t.getDisplays().filter(f=>!!f),i=s.find(f=>f.getId()===0)??s[0]??null,n=i?.getId()??0,o=this.debugDisplayOverrides.get(t.id),a=o?.origin??i?.getOrigin();a&&e.setOrigin(new g.Vector2(a.x,a.y));let r=o?.size??i?.getSize();if(r){let f=Math.min(256,Math.max(1,Math.round(r.x))),D=Math.min(256,Math.max(1,Math.round(r.y)));e.setSize(new g.Vector2(f,D))}let p=t.getCurrentPaletteSlotId(n);p!==null&&t.switchPalette(u.DEBUG_DISPLAY_ID,p),t.setScalingMode(u.DEBUG_DISPLAY_ID,g.ScalingMode.None);let l=t.getCellSize(n);t.setCellSize(u.DEBUG_DISPLAY_ID,l.cellWidth,l.cellHeight)}ensureDebugDisplay(t){let e=t.getDisplays().filter(i=>!!i),s=e.find(i=>i.getId()===u.DEBUG_DISPLAY_ID);if(!s){let i=e.find(a=>a.getId()===0)??e[0]??null,n=i?.getSize()??new g.Vector2(this.options.width,this.options.height);s=new v.Display(u.DEBUG_DISPLAY_ID,n.x,n.y);let o=i?.getOrigin()??new g.Vector2(0,0);s.setOrigin(new g.Vector2(o.x,o.y)),t.addDisplay(s)}return s}log(t){this.options.debug&&console.warn(`[ServerRuntime] ${t}`)}validateApplication(t){if(!t||typeof t!="object")throw new Error("[ServerRuntime] Invalid application: must be an object implementing IApplication interface");let e=t,s=["init","initUser","updateUser"],i=[];for(let n of s)typeof e[n]!="function"&&i.push(n);if(i.length>0)throw new Error(`[ServerRuntime] Invalid application: missing required methods: ${i.join(", ")}.
1
+ "use strict";var C=Object.create;var d=Object.defineProperty;var A=Object.getOwnPropertyDescriptor;var T=Object.getOwnPropertyNames;var z=Object.getPrototypeOf,M=Object.prototype.hasOwnProperty;var $=(c,t,e)=>t in c?d(c,t,{enumerable:!0,configurable:!0,writable:!0,value:e}):c[t]=e;var k=(c,t)=>d(c,"name",{value:t,configurable:!0});var x=(c,t)=>{for(var e in t)d(c,e,{get:t[e],enumerable:!0})},b=(c,t,e,s)=>{if(t&&typeof t=="object"||typeof t=="function")for(let i of T(t))!M.call(c,i)&&i!==e&&d(c,i,{get:()=>t[i],enumerable:!(s=A(t,i))||s.enumerable});return c};var R=(c,t,e)=>(e=c!=null?C(z(c)):{},b(t||!c||!c.__esModule?d(e,"default",{value:c,enumerable:!0}):e,c)),B=c=>b(d({},"__esModule",{value:!0}),c);var h=(c,t,e)=>($(c,typeof t!="symbol"?t+"":t,e),e);var I={};x(I,{ServerRuntime:()=>y});module.exports=B(I);var v=require("@utsp/core"),w=require("@utsp/network-server"),g=require("@utsp/types"),S=require("fs/promises"),m=R(require("path"),1),U=require("url"),P={};var u=class u{constructor(t){h(this,"core");h(this,"network");h(this,"options");h(this,"running",!1);h(this,"startTime",0);h(this,"lastTimestamp",0);h(this,"tickCount",0);h(this,"tps",0);h(this,"tpsUpdateTime",0);h(this,"tickInterval",null);h(this,"totalBytesSent",0);h(this,"debugClients",new Set);h(this,"debugDisplayOverrides",new Map);this.validateApplication(t.application),this.options={application:t.application,port:t.port,host:t.host??"0.0.0.0",tickRate:t.tickRate??20,maxConnections:t.maxConnections??100,debug:t.debug??!1,debugUi:t.debugUi??!1,debugUiServePath:t.debugUiServePath??this.resolveDefaultDebugUiServePath(),width:t.width??80,height:t.height??25,cors:t.cors??{origin:"*"}},this.log("Initializing ServerRuntime..."),this.core=new v.Core({mode:"server",maxUsers:this.options.maxConnections}),this.network=new w.SocketIOServer({port:this.options.port,host:this.options.host,cors:this.options.cors,maxConnections:this.options.maxConnections,debug:this.options.debug}),this.log("ServerRuntime initialized")}getMode(){return"server"}isRunning(){return this.running}setTickRate(t){if(t<=0||t>1e3)throw new Error(`Invalid tick rate: ${t}. Must be between 1 and 1000.`);if(this.options.tickRate=t,this.log(`Tick rate changed to ${t} TPS`),this.core.getUsers().forEach(e=>{e.setBytesTickRate(t)}),this.running&&this.tickInterval){clearInterval(this.tickInterval);let e=1e3/t;this.log(`Restarting tick loop at ${t} TPS (${e}ms)...`),this.tickInterval=setInterval(()=>{this.tick()},e)}}getTickRate(){return this.options.tickRate}async start(){if(this.running){this.log("Already running");return}this.log("Starting ServerRuntime..."),await this.network.start(),this.log(`Server listening on ${this.options.host}:${this.options.port}`),this.setupDebugUiStaticHosting(),this.setupNetworkHandlers(),this.log("Calling application.init()..."),await this.options.application.init(this.core,this),this.running=!0,this.startTime=Date.now(),this.lastTimestamp=this.startTime,this.tpsUpdateTime=this.startTime;let t=1e3/this.options.tickRate;this.log(`Starting tick loop at ${this.options.tickRate} TPS (${t}ms)...`),this.tickInterval=setInterval(()=>{this.tick()},t)}async stop(){if(!this.running)return;this.log("Stopping ServerRuntime..."),this.running=!1,this.tickInterval&&(clearInterval(this.tickInterval),this.tickInterval=null),this.network.getClients().forEach(e=>{this.network.disconnectClient(e,"Server stopped")}),await this.network.stop(),this.log("ServerRuntime stopped")}getStats(){return{mode:"server",running:this.running,userCount:this.core.getUsers().length,tps:this.tps,uptime:this.running?Date.now()-this.startTime:0,totalTicks:this.tickCount,totalBytesSent:this.totalBytesSent}}async destroy(){await this.stop(),this.log("Destroying ServerRuntime..."),this.options.application.destroy&&this.options.application.destroy(),await this.network.destroy(),this.log("ServerRuntime destroyed")}setupNetworkHandlers(){this.network.onConnect(t=>{this.log(`Client connected: ${t}`)}),this.network.onDisconnect((t,e)=>{this.log(`Client disconnected: ${t} (${e})`),this.debugClients.delete(t),this.debugDisplayOverrides.delete(t);let s=this.core.getUser(t);s&&(this.options.application.destroyUser&&this.options.application.destroyUser(this.core,s,e),this.core.removeUser(t))}),this.network.on("join",(t,e)=>{this.log(`Join request from ${t}: ${e.username}`);try{let s=e.username||`Player${t.substring(0,4)}`,i=this.core.createUser(t,s);i.setBytesTickRate(this.options.tickRate),this.options.application.initUser(this.core,i,{username:s,token:e.token}),this.options.debugUi&&this.syncDebugDisplay(i),this.sendAndCount(t,"join_response",{success:!0,userId:t,roomId:"main"});let n=this.core.generateAllLoadPackets();this.log(`Sending ${n.length} load packets to ${t}...`),n.forEach(p=>{this.sendAndCount(t,"load",p)});let o=this.core.generateMacroLoadPackets(t);o.length>0&&(this.log(`Sending ${o.length} macro load packets to ${t}...`),o.forEach(p=>{this.sendAndCount(t,"load",p)}));let a=i.getInputBindingsLoadPacket();a&&(this.sendAndCount(t,"input-bindings",a),this.log(`Sent input bindings to ${t}`));let r=this.core.generateInitialUpdatePacket(t);r&&(this.sendAndCount(t,"update",r),this.log(`Sent initial update packet to ${t}`)),this.log(`User ${s} (${t}) joined`)}catch(s){this.log(`Failed to join: ${s}`),this.sendAndCount(t,"join_response",{success:!1,error:"Failed to join game"})}}),this.network.on("leave",t=>{this.log(`Leave request from ${t}`),this.network.disconnectClient(t,"User left")}),this.network.on("input",(t,e)=>{let s=this.core.getUser(t);if(!s){this.log(`Input from unknown user: ${t}`);return}let i=e instanceof Uint8Array?e:new Uint8Array(e);s.decodeAndApplyCompressedInput(i),this.syncResponsiveDisplaySize(s)}),this.network.on("audio-ack",(t,e)=>{let s=this.core.getUser(t);if(!s){this.log(`Audio ACK from unknown user: ${t}`);return}s.handleAudioAck(e),this.options.application.onAudioAck&&this.options.application.onAudioAck(this.core,s,e),this.log(`Audio ACK from ${t}: ${e.type}`)}),this.network.onBridge("*",(t,e)=>{let s=this.core.getUser(t);if(!s){this.log(`Bridge message from unknown user: ${t}`);return}if(e.channel==="__debug_auth"){this.options.debugUi&&(this.debugClients.add(t),this.log(`Debug client authenticated: ${t}`));return}if(e.channel==="__debug_display_255"){this.handleDebugDisplayBridge(s,e.data);return}e.channel.startsWith("__debug_")||(this.options.application.onBridgeMessage&&this.options.application.onBridgeMessage(this.core,s,e.channel,e.data),this.log(`Bridge from ${t} on channel '${e.channel}'`))})}tick(){if(!this.running)return;let t=Date.now(),e=(t-this.lastTimestamp)/1e3;this.lastTimestamp=t,this.tickCount++,t-this.tpsUpdateTime>=1e3&&(this.tps=Math.round(this.tickCount*1e3/(t-this.tpsUpdateTime)),this.tickCount=0,this.tpsUpdateTime=t),this.options.application.update&&this.options.application.update(this.core,e),this.core.getUsers().forEach(i=>{if(this.options.application.updateUser(this.core,i,e),i.clearTextInputs(),this.options.debugUi&&this.syncDebugDisplay(i),i.needsSendSounds()){let n=this.core.generateSoundLoadPackets();n.length>0&&(this.log(`Sending ${n.length} sound packets to ${i.id}...`),n.forEach(o=>{this.sendAndCount(i.id,"sound-load",o)})),i.clearSendSounds()}}),this.broadcastUpdates()}broadcastUpdates(){let t=this.core.endTick(),e=new Map;if(t.forEach(({static:s,dynamic:i},n)=>{s&&(this.sendAndCount(n,"update-static",s),e.set(n,{staticSize:s.length,dynamicSize:e.get(n)?.dynamicSize??0})),i&&(this.sendVolatileAndCount(n,"update-dynamic",i),e.set(n,{staticSize:e.get(n)?.staticSize??0,dynamicSize:i.length}));let o=this.core.getUser(n);o&&o.hasBridgeMessages()&&o.getBridgeMessages().forEach(r=>{this.network.sendBridge(n,r.channel,r.data)})}),this.options.debugUi&&this.debugClients.size>0){let s=this.core.getUsers().map(n=>{let o=e.get(n.id)??{staticSize:0,dynamicSize:0},a=this.core.getStats().displayCompositeLayerIds,r={};for(let[p,l]of a.entries())r[p]={layerIdsTopToBottom:l};return{...n.getDebugInfo(),displayComposite:r,packets:o}}),i={tick:{tickRate:this.options.tickRate,tickCount:this.tickCount,tps:this.tps,uptime:this.running?Date.now()-this.startTime:0},users:s,totals:{totalBytesSent:this.totalBytesSent}};this.debugClients.forEach(n=>{this.network.sendBridge(n,"__debug_meta",i)})}}sendAndCount(t,e,s){this.network.sendToClient(t,e,s);let i=this.getDataSize(s);this.totalBytesSent+=i;let n=this.core.getUser(t);n&&n.recordBytesSent(i)}sendVolatileAndCount(t,e,s){let i=this.network;i.sendToClientVolatile?i.sendToClientVolatile(t,e,s):this.network.sendToClient(t,e,s),this.totalBytesSent+=s.length;let n=this.core.getUser(t);n&&n.recordBytesSent(s.length)}getDataSize(t){return t instanceof Uint8Array||typeof t=="string"?t.length:JSON.stringify(t).length}syncResponsiveDisplaySize(t){let e=t.getDisplays();if(e.length!==0)for(let s of e){let i=s.getId();if(t.getScalingMode(i)!=="responsive")continue;let o=t.getDisplayViewport(i);if(!o)continue;let a=t.getCellSize(i);if(a.cellWidth<=0||a.cellHeight<=0)continue;let r=Math.floor(o.pixelWidth/a.cellWidth),p=Math.floor(o.pixelHeight/a.cellHeight);r=Math.min(256,Math.max(1,r)),p=Math.min(256,Math.max(1,p));let l=s.getSize();(l.x!==r||l.y!==p)&&s.setSize(new g.Vector2(r,p))}}handleDebugDisplayBridge(t,e){if(!this.options.debugUi)return;let s=e&&typeof e=="object"?e:null;if(!s)return;let n={...this.debugDisplayOverrides.get(t.id)??{}},o=!1,a=s.origin;a&&typeof a.x=="number"&&typeof a.y=="number"&&(n.origin=new g.Vector2(a.x,a.y),o=!0);let r=s.size;if(r&&typeof r.x=="number"&&typeof r.y=="number"){let p=Math.min(256,Math.max(1,Math.round(r.x))),l=Math.min(256,Math.max(1,Math.round(r.y)));n.size=new g.Vector2(p,l),o=!0}o&&(this.debugDisplayOverrides.set(t.id,n),this.syncDebugDisplay(t))}syncDebugDisplay(t){if(!this.options.debugUi)return;let e=this.ensureDebugDisplay(t),s=t.getDisplays().filter(f=>!!f),i=s.find(f=>f.getId()===0)??s[0]??null,n=i?.getId()??0,o=this.debugDisplayOverrides.get(t.id),a=o?.origin??i?.getOrigin();a&&e.setOrigin(new g.Vector2(a.x,a.y));let r=o?.size??i?.getSize();if(r){let f=Math.min(256,Math.max(1,Math.round(r.x))),D=Math.min(256,Math.max(1,Math.round(r.y)));e.setSize(new g.Vector2(f,D))}i&&e.setRenderPasses(i.getRenderPasses());let p=t.getCurrentPaletteSlotId(n);p!==null&&t.switchPalette(u.DEBUG_DISPLAY_ID,p),t.setScalingMode(u.DEBUG_DISPLAY_ID,g.ScalingMode.None);let l=t.getCellSize(n);t.setCellSize(u.DEBUG_DISPLAY_ID,l.cellWidth,l.cellHeight)}ensureDebugDisplay(t){let e=t.getDisplays().filter(i=>!!i),s=e.find(i=>i.getId()===u.DEBUG_DISPLAY_ID);if(!s){let i=e.find(a=>a.getId()===0)??e[0]??null,n=i?.getSize()??new g.Vector2(this.options.width,this.options.height);s=new v.Display(u.DEBUG_DISPLAY_ID,n.x,n.y);let o=i?.getOrigin()??new g.Vector2(0,0);s.setOrigin(new g.Vector2(o.x,o.y)),t.addDisplay(s)}return s}log(t){this.options.debug&&console.warn(`[ServerRuntime] ${t}`)}validateApplication(t){if(!t||typeof t!="object")throw new Error("[ServerRuntime] Invalid application: must be an object implementing IApplication interface");let e=t,s=["init","initUser","updateUser"],i=[];for(let n of s)typeof e[n]!="function"&&i.push(n);if(i.length>0)throw new Error(`[ServerRuntime] Invalid application: missing required methods: ${i.join(", ")}.
2
2
  Your application must implement the IApplication interface from @utsp/types.
3
3
  Required methods:
4
4
  - init(core, runtime): Initialize application
package/dist/index.mjs CHANGED
@@ -1,4 +1,4 @@
1
- var y=Object.defineProperty;var k=(u,t,e)=>t in u?y(u,t,{enumerable:!0,configurable:!0,writable:!0,value:e}):u[t]=e;var v=(u,t)=>y(u,"name",{value:t,configurable:!0});var p=(u,t,e)=>(k(u,typeof t!="symbol"?t+"":t,e),e);import{Core as b,Display as w}from"@utsp/core";import{SocketIOServer as U}from"@utsp/network-server";import{ScalingMode as D,Vector2 as g}from"@utsp/types";import{readFile as C,stat as A}from"fs/promises";import m from"path";import{fileURLToPath as T}from"url";var l=class l{constructor(t){p(this,"core");p(this,"network");p(this,"options");p(this,"running",!1);p(this,"startTime",0);p(this,"lastTimestamp",0);p(this,"tickCount",0);p(this,"tps",0);p(this,"tpsUpdateTime",0);p(this,"tickInterval",null);p(this,"totalBytesSent",0);p(this,"debugClients",new Set);p(this,"debugDisplayOverrides",new Map);this.validateApplication(t.application),this.options={application:t.application,port:t.port,host:t.host??"0.0.0.0",tickRate:t.tickRate??20,maxConnections:t.maxConnections??100,debug:t.debug??!1,debugUi:t.debugUi??!1,debugUiServePath:t.debugUiServePath??this.resolveDefaultDebugUiServePath(),width:t.width??80,height:t.height??25,cors:t.cors??{origin:"*"}},this.log("Initializing ServerRuntime..."),this.core=new b({mode:"server",maxUsers:this.options.maxConnections}),this.network=new U({port:this.options.port,host:this.options.host,cors:this.options.cors,maxConnections:this.options.maxConnections,debug:this.options.debug}),this.log("ServerRuntime initialized")}getMode(){return"server"}isRunning(){return this.running}setTickRate(t){if(t<=0||t>1e3)throw new Error(`Invalid tick rate: ${t}. Must be between 1 and 1000.`);if(this.options.tickRate=t,this.log(`Tick rate changed to ${t} TPS`),this.core.getUsers().forEach(e=>{e.setBytesTickRate(t)}),this.running&&this.tickInterval){clearInterval(this.tickInterval);let e=1e3/t;this.log(`Restarting tick loop at ${t} TPS (${e}ms)...`),this.tickInterval=setInterval(()=>{this.tick()},e)}}getTickRate(){return this.options.tickRate}async start(){if(this.running){this.log("Already running");return}this.log("Starting ServerRuntime..."),await this.network.start(),this.log(`Server listening on ${this.options.host}:${this.options.port}`),this.setupDebugUiStaticHosting(),this.setupNetworkHandlers(),this.log("Calling application.init()..."),await this.options.application.init(this.core,this),this.running=!0,this.startTime=Date.now(),this.lastTimestamp=this.startTime,this.tpsUpdateTime=this.startTime;let t=1e3/this.options.tickRate;this.log(`Starting tick loop at ${this.options.tickRate} TPS (${t}ms)...`),this.tickInterval=setInterval(()=>{this.tick()},t)}async stop(){if(!this.running)return;this.log("Stopping ServerRuntime..."),this.running=!1,this.tickInterval&&(clearInterval(this.tickInterval),this.tickInterval=null),this.network.getClients().forEach(e=>{this.network.disconnectClient(e,"Server stopped")}),await this.network.stop(),this.log("ServerRuntime stopped")}getStats(){return{mode:"server",running:this.running,userCount:this.core.getUsers().length,tps:this.tps,uptime:this.running?Date.now()-this.startTime:0,totalTicks:this.tickCount,totalBytesSent:this.totalBytesSent}}async destroy(){await this.stop(),this.log("Destroying ServerRuntime..."),this.options.application.destroy&&this.options.application.destroy(),await this.network.destroy(),this.log("ServerRuntime destroyed")}setupNetworkHandlers(){this.network.onConnect(t=>{this.log(`Client connected: ${t}`)}),this.network.onDisconnect((t,e)=>{this.log(`Client disconnected: ${t} (${e})`),this.debugClients.delete(t),this.debugDisplayOverrides.delete(t);let i=this.core.getUser(t);i&&(this.options.application.destroyUser&&this.options.application.destroyUser(this.core,i,e),this.core.removeUser(t))}),this.network.on("join",(t,e)=>{this.log(`Join request from ${t}: ${e.username}`);try{let i=e.username||`Player${t.substring(0,4)}`,s=this.core.createUser(t,i);s.setBytesTickRate(this.options.tickRate),this.options.application.initUser(this.core,s,{username:i,token:e.token}),this.options.debugUi&&this.syncDebugDisplay(s),this.sendAndCount(t,"join_response",{success:!0,userId:t,roomId:"main"});let n=this.core.generateAllLoadPackets();this.log(`Sending ${n.length} load packets to ${t}...`),n.forEach(c=>{this.sendAndCount(t,"load",c)});let o=this.core.generateMacroLoadPackets(t);o.length>0&&(this.log(`Sending ${o.length} macro load packets to ${t}...`),o.forEach(c=>{this.sendAndCount(t,"load",c)}));let a=s.getInputBindingsLoadPacket();a&&(this.sendAndCount(t,"input-bindings",a),this.log(`Sent input bindings to ${t}`));let r=this.core.generateInitialUpdatePacket(t);r&&(this.sendAndCount(t,"update",r),this.log(`Sent initial update packet to ${t}`)),this.log(`User ${i} (${t}) joined`)}catch(i){this.log(`Failed to join: ${i}`),this.sendAndCount(t,"join_response",{success:!1,error:"Failed to join game"})}}),this.network.on("leave",t=>{this.log(`Leave request from ${t}`),this.network.disconnectClient(t,"User left")}),this.network.on("input",(t,e)=>{let i=this.core.getUser(t);if(!i){this.log(`Input from unknown user: ${t}`);return}let s=e instanceof Uint8Array?e:new Uint8Array(e);i.decodeAndApplyCompressedInput(s),this.syncResponsiveDisplaySize(i)}),this.network.on("audio-ack",(t,e)=>{let i=this.core.getUser(t);if(!i){this.log(`Audio ACK from unknown user: ${t}`);return}i.handleAudioAck(e),this.options.application.onAudioAck&&this.options.application.onAudioAck(this.core,i,e),this.log(`Audio ACK from ${t}: ${e.type}`)}),this.network.onBridge("*",(t,e)=>{let i=this.core.getUser(t);if(!i){this.log(`Bridge message from unknown user: ${t}`);return}if(e.channel==="__debug_auth"){this.options.debugUi&&(this.debugClients.add(t),this.log(`Debug client authenticated: ${t}`));return}if(e.channel==="__debug_display_255"){this.handleDebugDisplayBridge(i,e.data);return}e.channel.startsWith("__debug_")||(this.options.application.onBridgeMessage&&this.options.application.onBridgeMessage(this.core,i,e.channel,e.data),this.log(`Bridge from ${t} on channel '${e.channel}'`))})}tick(){if(!this.running)return;let t=Date.now(),e=(t-this.lastTimestamp)/1e3;this.lastTimestamp=t,this.tickCount++,t-this.tpsUpdateTime>=1e3&&(this.tps=Math.round(this.tickCount*1e3/(t-this.tpsUpdateTime)),this.tickCount=0,this.tpsUpdateTime=t),this.options.application.update&&this.options.application.update(this.core,e),this.core.getUsers().forEach(s=>{if(this.options.application.updateUser(this.core,s,e),s.clearTextInputs(),this.options.debugUi&&this.syncDebugDisplay(s),s.needsSendSounds()){let n=this.core.generateSoundLoadPackets();n.length>0&&(this.log(`Sending ${n.length} sound packets to ${s.id}...`),n.forEach(o=>{this.sendAndCount(s.id,"sound-load",o)})),s.clearSendSounds()}}),this.broadcastUpdates()}broadcastUpdates(){let t=this.core.endTick(),e=new Map;if(t.forEach(({static:i,dynamic:s},n)=>{i&&(this.sendAndCount(n,"update-static",i),e.set(n,{staticSize:i.length,dynamicSize:e.get(n)?.dynamicSize??0})),s&&(this.sendVolatileAndCount(n,"update-dynamic",s),e.set(n,{staticSize:e.get(n)?.staticSize??0,dynamicSize:s.length}));let o=this.core.getUser(n);o&&o.hasBridgeMessages()&&o.getBridgeMessages().forEach(r=>{this.network.sendBridge(n,r.channel,r.data)})}),this.options.debugUi&&this.debugClients.size>0){let i=this.core.getUsers().map(n=>{let o=e.get(n.id)??{staticSize:0,dynamicSize:0},a=this.core.getStats().displayCompositeLayerIds,r={};for(let[c,h]of a.entries())r[c]={layerIdsTopToBottom:h};return{...n.getDebugInfo(),displayComposite:r,packets:o}}),s={tick:{tickRate:this.options.tickRate,tickCount:this.tickCount,tps:this.tps,uptime:this.running?Date.now()-this.startTime:0},users:i,totals:{totalBytesSent:this.totalBytesSent}};this.debugClients.forEach(n=>{this.network.sendBridge(n,"__debug_meta",s)})}}sendAndCount(t,e,i){this.network.sendToClient(t,e,i);let s=this.getDataSize(i);this.totalBytesSent+=s;let n=this.core.getUser(t);n&&n.recordBytesSent(s)}sendVolatileAndCount(t,e,i){let s=this.network;s.sendToClientVolatile?s.sendToClientVolatile(t,e,i):this.network.sendToClient(t,e,i),this.totalBytesSent+=i.length;let n=this.core.getUser(t);n&&n.recordBytesSent(i.length)}getDataSize(t){return t instanceof Uint8Array||typeof t=="string"?t.length:JSON.stringify(t).length}syncResponsiveDisplaySize(t){let e=t.getDisplays();if(e.length!==0)for(let i of e){let s=i.getId();if(t.getScalingMode(s)!=="responsive")continue;let o=t.getDisplayViewport(s);if(!o)continue;let a=t.getCellSize(s);if(a.cellWidth<=0||a.cellHeight<=0)continue;let r=Math.floor(o.pixelWidth/a.cellWidth),c=Math.floor(o.pixelHeight/a.cellHeight);r=Math.min(256,Math.max(1,r)),c=Math.min(256,Math.max(1,c));let h=i.getSize();(h.x!==r||h.y!==c)&&i.setSize(new g(r,c))}}handleDebugDisplayBridge(t,e){if(!this.options.debugUi)return;let i=e&&typeof e=="object"?e:null;if(!i)return;let n={...this.debugDisplayOverrides.get(t.id)??{}},o=!1,a=i.origin;a&&typeof a.x=="number"&&typeof a.y=="number"&&(n.origin=new g(a.x,a.y),o=!0);let r=i.size;if(r&&typeof r.x=="number"&&typeof r.y=="number"){let c=Math.min(256,Math.max(1,Math.round(r.x))),h=Math.min(256,Math.max(1,Math.round(r.y)));n.size=new g(c,h),o=!0}o&&(this.debugDisplayOverrides.set(t.id,n),this.syncDebugDisplay(t))}syncDebugDisplay(t){if(!this.options.debugUi)return;let e=this.ensureDebugDisplay(t),i=t.getDisplays().filter(d=>!!d),s=i.find(d=>d.getId()===0)??i[0]??null,n=s?.getId()??0,o=this.debugDisplayOverrides.get(t.id),a=o?.origin??s?.getOrigin();a&&e.setOrigin(new g(a.x,a.y));let r=o?.size??s?.getSize();if(r){let d=Math.min(256,Math.max(1,Math.round(r.x))),S=Math.min(256,Math.max(1,Math.round(r.y)));e.setSize(new g(d,S))}let c=t.getCurrentPaletteSlotId(n);c!==null&&t.switchPalette(l.DEBUG_DISPLAY_ID,c),t.setScalingMode(l.DEBUG_DISPLAY_ID,D.None);let h=t.getCellSize(n);t.setCellSize(l.DEBUG_DISPLAY_ID,h.cellWidth,h.cellHeight)}ensureDebugDisplay(t){let e=t.getDisplays().filter(s=>!!s),i=e.find(s=>s.getId()===l.DEBUG_DISPLAY_ID);if(!i){let s=e.find(a=>a.getId()===0)??e[0]??null,n=s?.getSize()??new g(this.options.width,this.options.height);i=new w(l.DEBUG_DISPLAY_ID,n.x,n.y);let o=s?.getOrigin()??new g(0,0);i.setOrigin(new g(o.x,o.y)),t.addDisplay(i)}return i}log(t){this.options.debug&&console.warn(`[ServerRuntime] ${t}`)}validateApplication(t){if(!t||typeof t!="object")throw new Error("[ServerRuntime] Invalid application: must be an object implementing IApplication interface");let e=t,i=["init","initUser","updateUser"],s=[];for(let n of i)typeof e[n]!="function"&&s.push(n);if(s.length>0)throw new Error(`[ServerRuntime] Invalid application: missing required methods: ${s.join(", ")}.
1
+ var y=Object.defineProperty;var k=(u,t,e)=>t in u?y(u,t,{enumerable:!0,configurable:!0,writable:!0,value:e}):u[t]=e;var v=(u,t)=>y(u,"name",{value:t,configurable:!0});var p=(u,t,e)=>(k(u,typeof t!="symbol"?t+"":t,e),e);import{Core as b,Display as w}from"@utsp/core";import{SocketIOServer as U}from"@utsp/network-server";import{ScalingMode as D,Vector2 as g}from"@utsp/types";import{readFile as C,stat as A}from"fs/promises";import m from"path";import{fileURLToPath as T}from"url";var l=class l{constructor(t){p(this,"core");p(this,"network");p(this,"options");p(this,"running",!1);p(this,"startTime",0);p(this,"lastTimestamp",0);p(this,"tickCount",0);p(this,"tps",0);p(this,"tpsUpdateTime",0);p(this,"tickInterval",null);p(this,"totalBytesSent",0);p(this,"debugClients",new Set);p(this,"debugDisplayOverrides",new Map);this.validateApplication(t.application),this.options={application:t.application,port:t.port,host:t.host??"0.0.0.0",tickRate:t.tickRate??20,maxConnections:t.maxConnections??100,debug:t.debug??!1,debugUi:t.debugUi??!1,debugUiServePath:t.debugUiServePath??this.resolveDefaultDebugUiServePath(),width:t.width??80,height:t.height??25,cors:t.cors??{origin:"*"}},this.log("Initializing ServerRuntime..."),this.core=new b({mode:"server",maxUsers:this.options.maxConnections}),this.network=new U({port:this.options.port,host:this.options.host,cors:this.options.cors,maxConnections:this.options.maxConnections,debug:this.options.debug}),this.log("ServerRuntime initialized")}getMode(){return"server"}isRunning(){return this.running}setTickRate(t){if(t<=0||t>1e3)throw new Error(`Invalid tick rate: ${t}. Must be between 1 and 1000.`);if(this.options.tickRate=t,this.log(`Tick rate changed to ${t} TPS`),this.core.getUsers().forEach(e=>{e.setBytesTickRate(t)}),this.running&&this.tickInterval){clearInterval(this.tickInterval);let e=1e3/t;this.log(`Restarting tick loop at ${t} TPS (${e}ms)...`),this.tickInterval=setInterval(()=>{this.tick()},e)}}getTickRate(){return this.options.tickRate}async start(){if(this.running){this.log("Already running");return}this.log("Starting ServerRuntime..."),await this.network.start(),this.log(`Server listening on ${this.options.host}:${this.options.port}`),this.setupDebugUiStaticHosting(),this.setupNetworkHandlers(),this.log("Calling application.init()..."),await this.options.application.init(this.core,this),this.running=!0,this.startTime=Date.now(),this.lastTimestamp=this.startTime,this.tpsUpdateTime=this.startTime;let t=1e3/this.options.tickRate;this.log(`Starting tick loop at ${this.options.tickRate} TPS (${t}ms)...`),this.tickInterval=setInterval(()=>{this.tick()},t)}async stop(){if(!this.running)return;this.log("Stopping ServerRuntime..."),this.running=!1,this.tickInterval&&(clearInterval(this.tickInterval),this.tickInterval=null),this.network.getClients().forEach(e=>{this.network.disconnectClient(e,"Server stopped")}),await this.network.stop(),this.log("ServerRuntime stopped")}getStats(){return{mode:"server",running:this.running,userCount:this.core.getUsers().length,tps:this.tps,uptime:this.running?Date.now()-this.startTime:0,totalTicks:this.tickCount,totalBytesSent:this.totalBytesSent}}async destroy(){await this.stop(),this.log("Destroying ServerRuntime..."),this.options.application.destroy&&this.options.application.destroy(),await this.network.destroy(),this.log("ServerRuntime destroyed")}setupNetworkHandlers(){this.network.onConnect(t=>{this.log(`Client connected: ${t}`)}),this.network.onDisconnect((t,e)=>{this.log(`Client disconnected: ${t} (${e})`),this.debugClients.delete(t),this.debugDisplayOverrides.delete(t);let s=this.core.getUser(t);s&&(this.options.application.destroyUser&&this.options.application.destroyUser(this.core,s,e),this.core.removeUser(t))}),this.network.on("join",(t,e)=>{this.log(`Join request from ${t}: ${e.username}`);try{let s=e.username||`Player${t.substring(0,4)}`,i=this.core.createUser(t,s);i.setBytesTickRate(this.options.tickRate),this.options.application.initUser(this.core,i,{username:s,token:e.token}),this.options.debugUi&&this.syncDebugDisplay(i),this.sendAndCount(t,"join_response",{success:!0,userId:t,roomId:"main"});let n=this.core.generateAllLoadPackets();this.log(`Sending ${n.length} load packets to ${t}...`),n.forEach(c=>{this.sendAndCount(t,"load",c)});let o=this.core.generateMacroLoadPackets(t);o.length>0&&(this.log(`Sending ${o.length} macro load packets to ${t}...`),o.forEach(c=>{this.sendAndCount(t,"load",c)}));let a=i.getInputBindingsLoadPacket();a&&(this.sendAndCount(t,"input-bindings",a),this.log(`Sent input bindings to ${t}`));let r=this.core.generateInitialUpdatePacket(t);r&&(this.sendAndCount(t,"update",r),this.log(`Sent initial update packet to ${t}`)),this.log(`User ${s} (${t}) joined`)}catch(s){this.log(`Failed to join: ${s}`),this.sendAndCount(t,"join_response",{success:!1,error:"Failed to join game"})}}),this.network.on("leave",t=>{this.log(`Leave request from ${t}`),this.network.disconnectClient(t,"User left")}),this.network.on("input",(t,e)=>{let s=this.core.getUser(t);if(!s){this.log(`Input from unknown user: ${t}`);return}let i=e instanceof Uint8Array?e:new Uint8Array(e);s.decodeAndApplyCompressedInput(i),this.syncResponsiveDisplaySize(s)}),this.network.on("audio-ack",(t,e)=>{let s=this.core.getUser(t);if(!s){this.log(`Audio ACK from unknown user: ${t}`);return}s.handleAudioAck(e),this.options.application.onAudioAck&&this.options.application.onAudioAck(this.core,s,e),this.log(`Audio ACK from ${t}: ${e.type}`)}),this.network.onBridge("*",(t,e)=>{let s=this.core.getUser(t);if(!s){this.log(`Bridge message from unknown user: ${t}`);return}if(e.channel==="__debug_auth"){this.options.debugUi&&(this.debugClients.add(t),this.log(`Debug client authenticated: ${t}`));return}if(e.channel==="__debug_display_255"){this.handleDebugDisplayBridge(s,e.data);return}e.channel.startsWith("__debug_")||(this.options.application.onBridgeMessage&&this.options.application.onBridgeMessage(this.core,s,e.channel,e.data),this.log(`Bridge from ${t} on channel '${e.channel}'`))})}tick(){if(!this.running)return;let t=Date.now(),e=(t-this.lastTimestamp)/1e3;this.lastTimestamp=t,this.tickCount++,t-this.tpsUpdateTime>=1e3&&(this.tps=Math.round(this.tickCount*1e3/(t-this.tpsUpdateTime)),this.tickCount=0,this.tpsUpdateTime=t),this.options.application.update&&this.options.application.update(this.core,e),this.core.getUsers().forEach(i=>{if(this.options.application.updateUser(this.core,i,e),i.clearTextInputs(),this.options.debugUi&&this.syncDebugDisplay(i),i.needsSendSounds()){let n=this.core.generateSoundLoadPackets();n.length>0&&(this.log(`Sending ${n.length} sound packets to ${i.id}...`),n.forEach(o=>{this.sendAndCount(i.id,"sound-load",o)})),i.clearSendSounds()}}),this.broadcastUpdates()}broadcastUpdates(){let t=this.core.endTick(),e=new Map;if(t.forEach(({static:s,dynamic:i},n)=>{s&&(this.sendAndCount(n,"update-static",s),e.set(n,{staticSize:s.length,dynamicSize:e.get(n)?.dynamicSize??0})),i&&(this.sendVolatileAndCount(n,"update-dynamic",i),e.set(n,{staticSize:e.get(n)?.staticSize??0,dynamicSize:i.length}));let o=this.core.getUser(n);o&&o.hasBridgeMessages()&&o.getBridgeMessages().forEach(r=>{this.network.sendBridge(n,r.channel,r.data)})}),this.options.debugUi&&this.debugClients.size>0){let s=this.core.getUsers().map(n=>{let o=e.get(n.id)??{staticSize:0,dynamicSize:0},a=this.core.getStats().displayCompositeLayerIds,r={};for(let[c,h]of a.entries())r[c]={layerIdsTopToBottom:h};return{...n.getDebugInfo(),displayComposite:r,packets:o}}),i={tick:{tickRate:this.options.tickRate,tickCount:this.tickCount,tps:this.tps,uptime:this.running?Date.now()-this.startTime:0},users:s,totals:{totalBytesSent:this.totalBytesSent}};this.debugClients.forEach(n=>{this.network.sendBridge(n,"__debug_meta",i)})}}sendAndCount(t,e,s){this.network.sendToClient(t,e,s);let i=this.getDataSize(s);this.totalBytesSent+=i;let n=this.core.getUser(t);n&&n.recordBytesSent(i)}sendVolatileAndCount(t,e,s){let i=this.network;i.sendToClientVolatile?i.sendToClientVolatile(t,e,s):this.network.sendToClient(t,e,s),this.totalBytesSent+=s.length;let n=this.core.getUser(t);n&&n.recordBytesSent(s.length)}getDataSize(t){return t instanceof Uint8Array||typeof t=="string"?t.length:JSON.stringify(t).length}syncResponsiveDisplaySize(t){let e=t.getDisplays();if(e.length!==0)for(let s of e){let i=s.getId();if(t.getScalingMode(i)!=="responsive")continue;let o=t.getDisplayViewport(i);if(!o)continue;let a=t.getCellSize(i);if(a.cellWidth<=0||a.cellHeight<=0)continue;let r=Math.floor(o.pixelWidth/a.cellWidth),c=Math.floor(o.pixelHeight/a.cellHeight);r=Math.min(256,Math.max(1,r)),c=Math.min(256,Math.max(1,c));let h=s.getSize();(h.x!==r||h.y!==c)&&s.setSize(new g(r,c))}}handleDebugDisplayBridge(t,e){if(!this.options.debugUi)return;let s=e&&typeof e=="object"?e:null;if(!s)return;let n={...this.debugDisplayOverrides.get(t.id)??{}},o=!1,a=s.origin;a&&typeof a.x=="number"&&typeof a.y=="number"&&(n.origin=new g(a.x,a.y),o=!0);let r=s.size;if(r&&typeof r.x=="number"&&typeof r.y=="number"){let c=Math.min(256,Math.max(1,Math.round(r.x))),h=Math.min(256,Math.max(1,Math.round(r.y)));n.size=new g(c,h),o=!0}o&&(this.debugDisplayOverrides.set(t.id,n),this.syncDebugDisplay(t))}syncDebugDisplay(t){if(!this.options.debugUi)return;let e=this.ensureDebugDisplay(t),s=t.getDisplays().filter(d=>!!d),i=s.find(d=>d.getId()===0)??s[0]??null,n=i?.getId()??0,o=this.debugDisplayOverrides.get(t.id),a=o?.origin??i?.getOrigin();a&&e.setOrigin(new g(a.x,a.y));let r=o?.size??i?.getSize();if(r){let d=Math.min(256,Math.max(1,Math.round(r.x))),S=Math.min(256,Math.max(1,Math.round(r.y)));e.setSize(new g(d,S))}i&&e.setRenderPasses(i.getRenderPasses());let c=t.getCurrentPaletteSlotId(n);c!==null&&t.switchPalette(l.DEBUG_DISPLAY_ID,c),t.setScalingMode(l.DEBUG_DISPLAY_ID,D.None);let h=t.getCellSize(n);t.setCellSize(l.DEBUG_DISPLAY_ID,h.cellWidth,h.cellHeight)}ensureDebugDisplay(t){let e=t.getDisplays().filter(i=>!!i),s=e.find(i=>i.getId()===l.DEBUG_DISPLAY_ID);if(!s){let i=e.find(a=>a.getId()===0)??e[0]??null,n=i?.getSize()??new g(this.options.width,this.options.height);s=new w(l.DEBUG_DISPLAY_ID,n.x,n.y);let o=i?.getOrigin()??new g(0,0);s.setOrigin(new g(o.x,o.y)),t.addDisplay(s)}return s}log(t){this.options.debug&&console.warn(`[ServerRuntime] ${t}`)}validateApplication(t){if(!t||typeof t!="object")throw new Error("[ServerRuntime] Invalid application: must be an object implementing IApplication interface");let e=t,s=["init","initUser","updateUser"],i=[];for(let n of s)typeof e[n]!="function"&&i.push(n);if(i.length>0)throw new Error(`[ServerRuntime] Invalid application: missing required methods: ${i.join(", ")}.
2
2
  Your application must implement the IApplication interface from @utsp/types.
3
3
  Required methods:
4
4
  - init(core, runtime): Initialize application
@@ -9,4 +9,4 @@ Optional methods:
9
9
  - destroyUser(core, user, reason?): Cleanup when user disconnects
10
10
  - destroy(): Cleanup on shutdown
11
11
  - onAudioAck(core, user, ack): Handle audio acknowledgments
12
- - onBridgeMessage(core, user, channel, data): Handle bridge messages`)}setupDebugUiStaticHosting(){if(!this.options.debugUi||!this.options.debugUiServePath)return;let t=this.network.getHttpServer?.();if(!t){this.log("Debug UI serving skipped: HTTP server not available");return}let e=m.resolve(this.options.debugUiServePath);t.on("request",(i,s)=>{let n=i.url||"/";if(n.startsWith("/socket.io"))return;let o=n.split("?")[0],a=o==="/"?"/index.html":o,r=m.join(e,a);(async()=>{try{if(!(await A(r)).isFile()){s.statusCode=404,s.end("Not Found");return}let h=await C(r);s.statusCode=200,s.setHeader("Content-Type",this.getContentType(r)),s.end(h)}catch{s.statusCode=404,s.end("Not Found")}})().catch(()=>{s.statusCode=500,s.end("Internal Server Error")})}),this.log(`Debug UI static hosting enabled from ${e}`)}getContentType(t){switch(m.extname(t).toLowerCase()){case".html":return"text/html; charset=utf-8";case".js":return"application/javascript; charset=utf-8";case".css":return"text/css; charset=utf-8";case".json":return"application/json; charset=utf-8";case".map":return"application/json; charset=utf-8";case".svg":return"image/svg+xml";case".png":return"image/png";case".jpg":case".jpeg":return"image/jpeg";case".gif":return"image/gif";case".woff":return"font/woff";case".woff2":return"font/woff2";default:return"application/octet-stream"}}resolveDefaultDebugUiServePath(){let t=typeof __dirname<"u"?__dirname:m.dirname(T(import.meta.url));return m.resolve(t,"debug-ui")}};v(l,"ServerRuntime"),p(l,"DEBUG_DISPLAY_ID",255);var f=l;export{f as ServerRuntime};
12
+ - onBridgeMessage(core, user, channel, data): Handle bridge messages`)}setupDebugUiStaticHosting(){if(!this.options.debugUi||!this.options.debugUiServePath)return;let t=this.network.getHttpServer?.();if(!t){this.log("Debug UI serving skipped: HTTP server not available");return}let e=m.resolve(this.options.debugUiServePath);t.on("request",(s,i)=>{let n=s.url||"/";if(n.startsWith("/socket.io"))return;let o=n.split("?")[0],a=o==="/"?"/index.html":o,r=m.join(e,a);(async()=>{try{if(!(await A(r)).isFile()){i.statusCode=404,i.end("Not Found");return}let h=await C(r);i.statusCode=200,i.setHeader("Content-Type",this.getContentType(r)),i.end(h)}catch{i.statusCode=404,i.end("Not Found")}})().catch(()=>{i.statusCode=500,i.end("Internal Server Error")})}),this.log(`Debug UI static hosting enabled from ${e}`)}getContentType(t){switch(m.extname(t).toLowerCase()){case".html":return"text/html; charset=utf-8";case".js":return"application/javascript; charset=utf-8";case".css":return"text/css; charset=utf-8";case".json":return"application/json; charset=utf-8";case".map":return"application/json; charset=utf-8";case".svg":return"image/svg+xml";case".png":return"image/png";case".jpg":case".jpeg":return"image/jpeg";case".gif":return"image/gif";case".woff":return"font/woff";case".woff2":return"font/woff2";default:return"application/octet-stream"}}resolveDefaultDebugUiServePath(){let t=typeof __dirname<"u"?__dirname:m.dirname(T(import.meta.url));return m.resolve(t,"debug-ui")}};v(l,"ServerRuntime"),p(l,"DEBUG_DISPLAY_ID",255);var f=l;export{f as ServerRuntime};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@utsp/runtime-server",
3
- "version": "0.16.0-nightly.20251231153713.b870da1",
3
+ "version": "0.16.0-nightly.20260110151828.b70d960",
4
4
  "description": "Server-side runtime for UTSP applications (Node.js, headless)",
5
5
  "author": "THP Software",
6
6
  "license": "MIT",
@@ -46,12 +46,12 @@
46
46
  "access": "public"
47
47
  },
48
48
  "dependencies": {
49
- "@utsp/core": "0.16.0-nightly.20251231153713.b870da1",
50
- "@utsp/network-server": "0.16.0-nightly.20251231153713.b870da1",
51
- "@utsp/types": "0.16.0-nightly.20251231153713.b870da1"
49
+ "@utsp/core": "0.16.0-nightly.20260110151828.b70d960",
50
+ "@utsp/network-server": "0.16.0-nightly.20260110151828.b70d960",
51
+ "@utsp/types": "0.16.0-nightly.20260110151828.b70d960"
52
52
  },
53
53
  "devDependencies": {
54
- "@utsp/visual-debugger": "0.16.0-nightly.20251231153713.b870da1",
54
+ "@utsp/visual-debugger": "0.16.0-nightly.20260110151828.b70d960",
55
55
  "@types/node": "^20.0.0",
56
56
  "typescript": "^5.3.3"
57
57
  },