@utsp/runtime-server 0.12.0 → 0.13.0-nightly.20251214163808.a699d8b

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 CHANGED
@@ -1 +1 @@
1
- "use strict";var p=Object.defineProperty;var k=Object.getOwnPropertyDescriptor;var v=Object.getOwnPropertyNames;var f=Object.prototype.hasOwnProperty;var w=(o,t,e)=>t in o?p(o,t,{enumerable:!0,configurable:!0,writable:!0,value:e}):o[t]=e;var g=(o,t)=>p(o,"name",{value:t,configurable:!0});var S=(o,t)=>{for(var e in t)p(o,e,{get:t[e],enumerable:!0})},T=(o,t,e,i)=>{if(t&&typeof t=="object"||typeof t=="function")for(let s of v(t))!f.call(o,s)&&s!==e&&p(o,s,{get:()=>t[s],enumerable:!(i=k(t,s))||i.enumerable});return o};var y=o=>T(p({},"__esModule",{value:!0}),o);var r=(o,t,e)=>(w(o,typeof t!="symbol"?t+"":t,e),e);var $={};S($,{ServerRuntime:()=>l});module.exports=y($);var d=require("@utsp/core"),m=require("@utsp/network-server");var u=class u{constructor(t){r(this,"core");r(this,"network");r(this,"options");r(this,"running",!1);r(this,"startTime",0);r(this,"lastTimestamp",0);r(this,"tickCount",0);r(this,"tps",0);r(this,"tpsUpdateTime",0);r(this,"tickInterval",null);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,width:t.width??80,height:t.height??25,cors:t.cors??{origin:"*"}},this.log("Initializing ServerRuntime..."),this.core=new d.Core({mode:"server",maxUsers:this.options.maxConnections}),this.network=new m.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.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.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}}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})`);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);this.options.application.initUser(this.core,s,{username:i,token:e.token}),this.network.sendToClient(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.network.sendToClient(t,"load",c)});let a=this.core.generateMacroLoadPackets(t);a.length>0&&(this.log(`Sending ${a.length} macro load packets to ${t}...`),a.forEach(c=>{this.network.sendToClient(t,"load",c)}));let h=s.getInputBindingsLoadPacket();h&&(this.network.sendToClient(t,"input-bindings",h),this.log(`Sent input bindings to ${t}`)),this.log(`User ${i} (${t}) joined`)}catch(i){this.log(`Failed to join: ${i}`),this.network.sendToClient(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}if(e.axes&&Object.entries(e.axes).forEach(([s,n])=>{i.setAxis(s,n)}),e.buttons&&Object.entries(e.buttons).forEach(([s,n])=>{i.setButton(s,n)}),e.mousePosition){let{x:s,y:n}=e.mousePosition;i.setMousePosition(s,n,!0)}e.textInputs&&Array.isArray(e.textInputs)&&i.setTextInputs(e.textInputs)}),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}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(),s.needsSendSounds()){let n=this.core.generateSoundLoadPackets();n.length>0&&(this.log(`Sending ${n.length} sound packets to ${s.id}...`),n.forEach(a=>{this.network.sendToClient(s.id,"sound-load",a)})),s.clearSendSounds()}}),this.core.endTick(),this.broadcastUpdates()}broadcastUpdates(){let t=this.core.endTickSplit(),e=this.core.getCurrentTick();t.forEach(({static:i,dynamic:s},n)=>{if(i&&(this.network.sendToClient(n,"update-static",i),console.warn(`[SERVER] update-static: ${i.length}B (tick ${e})`)),s){let h=this.network;h.sendToClientVolatile?h.sendToClientVolatile(n,"update-dynamic",s):this.network.sendToClient(n,"update-dynamic",s),console.warn(`[SERVER] update-dynamic: ${s.length}B (tick ${e})`)}let a=this.core.getUser(n);a&&a.hasBridgeMessages()&&a.getBridgeMessages().forEach(c=>{this.network.sendBridge(n,c.channel,c.data)})})}log(t){this.options.debug&&console.warn(`[ServerRuntime] ${t}`)}};g(u,"ServerRuntime");var l=u;0&&(module.exports={ServerRuntime});
1
+ "use strict";var c=Object.defineProperty;var k=Object.getOwnPropertyDescriptor;var f=Object.getOwnPropertyNames;var v=Object.prototype.hasOwnProperty;var y=(o,t,e)=>t in o?c(o,t,{enumerable:!0,configurable:!0,writable:!0,value:e}):o[t]=e;var l=(o,t)=>c(o,"name",{value:t,configurable:!0});var S=(o,t)=>{for(var e in t)c(o,e,{get:t[e],enumerable:!0})},w=(o,t,e,i)=>{if(t&&typeof t=="object"||typeof t=="function")for(let s of f(t))!v.call(o,s)&&s!==e&&c(o,s,{get:()=>t[s],enumerable:!(i=k(t,s))||i.enumerable});return o};var C=o=>w(c({},"__esModule",{value:!0}),o);var r=(o,t,e)=>(y(o,typeof t!="symbol"?t+"":t,e),e);var $={};S($,{ServerRuntime:()=>p});module.exports=C($);var d=require("@utsp/core"),m=require("@utsp/network-server");var g=class g{constructor(t){r(this,"core");r(this,"network");r(this,"options");r(this,"running",!1);r(this,"startTime",0);r(this,"lastTimestamp",0);r(this,"tickCount",0);r(this,"tps",0);r(this,"tpsUpdateTime",0);r(this,"tickInterval",null);r(this,"totalBytesSent",0);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,width:t.width??80,height:t.height??25,cors:t.cors??{origin:"*"}},this.log("Initializing ServerRuntime..."),this.core=new d.Core({mode:"server",maxUsers:this.options.maxConnections}),this.network=new m.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.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})`);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.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(h=>{this.sendAndCount(t,"load",h)});let a=this.core.generateMacroLoadPackets(t);a.length>0&&(this.log(`Sending ${a.length} macro load packets to ${t}...`),a.forEach(h=>{this.sendAndCount(t,"load",h)}));let u=s.getInputBindingsLoadPacket();u&&(this.sendAndCount(t,"input-bindings",u),this.log(`Sent input bindings 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}if(e.axes&&Object.entries(e.axes).forEach(([s,n])=>{i.setAxis(s,n)}),e.buttons&&Object.entries(e.buttons).forEach(([s,n])=>{i.setButton(s,n)}),e.mousePosition){let{x:s,y:n}=e.mousePosition;i.setMousePosition(s,n,!0)}e.textInputs&&Array.isArray(e.textInputs)&&i.setTextInputs(e.textInputs)}),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}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(),s.needsSendSounds()){let n=this.core.generateSoundLoadPackets();n.length>0&&(this.log(`Sending ${n.length} sound packets to ${s.id}...`),n.forEach(a=>{this.sendAndCount(s.id,"sound-load",a)})),s.clearSendSounds()}}),this.core.endTick(),this.broadcastUpdates()}broadcastUpdates(){let t=this.core.endTickSplit(),e=this.core.getCurrentTick();t.forEach(({static:i,dynamic:s},n)=>{i&&(this.sendAndCount(n,"update-static",i),console.warn(`[SERVER] update-static: ${i.length}B (tick ${e})`)),s&&(this.sendVolatileAndCount(n,"update-dynamic",s),console.warn(`[SERVER] update-dynamic: ${s.length}B (tick ${e})`));let a=this.core.getUser(n);a&&a.hasBridgeMessages()&&a.getBridgeMessages().forEach(h=>{this.network.sendBridge(n,h.channel,h.data)})})}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}log(t){this.options.debug&&console.warn(`[ServerRuntime] ${t}`)}};l(g,"ServerRuntime");var p=g;0&&(module.exports={ServerRuntime});
package/dist/index.d.ts CHANGED
@@ -67,6 +67,8 @@ interface ServerRuntimeStats {
67
67
  uptime: number;
68
68
  /** Total ticks processed */
69
69
  totalTicks: number;
70
+ /** Total bytes sent to all clients (excluding bridge messages) */
71
+ totalBytesSent: number;
70
72
  }
71
73
 
72
74
  /**
@@ -93,6 +95,7 @@ declare class ServerRuntime {
93
95
  private tps;
94
96
  private tpsUpdateTime;
95
97
  private tickInterval;
98
+ private totalBytesSent;
96
99
  constructor(options: ServerRuntimeOptions);
97
100
  getMode(): 'server';
98
101
  isRunning(): boolean;
@@ -134,6 +137,24 @@ declare class ServerRuntime {
134
137
  * - Bridge messages → Sent after updates for synchronization
135
138
  */
136
139
  private broadcastUpdates;
140
+ /**
141
+ * Send data to client and count bytes
142
+ * @param clientId - Client ID
143
+ * @param event - Event name
144
+ * @param data - Data to send (Uint8Array for binary, any for JSON)
145
+ */
146
+ private sendAndCount;
147
+ /**
148
+ * Send data to client via volatile channel and count bytes
149
+ * @param clientId - Client ID
150
+ * @param event - Event name
151
+ * @param data - Data to send
152
+ */
153
+ private sendVolatileAndCount;
154
+ /**
155
+ * Get size of data in bytes
156
+ */
157
+ private getDataSize;
137
158
  /**
138
159
  * Debug logging
139
160
  */
package/dist/index.mjs CHANGED
@@ -1 +1 @@
1
- var u=Object.defineProperty;var d=(a,t,e)=>t in a?u(a,t,{enumerable:!0,configurable:!0,writable:!0,value:e}):a[t]=e;var g=(a,t)=>u(a,"name",{value:t,configurable:!0});var o=(a,t,e)=>(d(a,typeof t!="symbol"?t+"":t,e),e);import{Core as m}from"@utsp/core";import{SocketIOServer as k}from"@utsp/network-server";var l=class l{constructor(t){o(this,"core");o(this,"network");o(this,"options");o(this,"running",!1);o(this,"startTime",0);o(this,"lastTimestamp",0);o(this,"tickCount",0);o(this,"tps",0);o(this,"tpsUpdateTime",0);o(this,"tickInterval",null);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,width:t.width??80,height:t.height??25,cors:t.cors??{origin:"*"}},this.log("Initializing ServerRuntime..."),this.core=new m({mode:"server",maxUsers:this.options.maxConnections}),this.network=new k({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.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.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}}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})`);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);this.options.application.initUser(this.core,s,{username:i,token:e.token}),this.network.sendToClient(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.network.sendToClient(t,"load",c)});let r=this.core.generateMacroLoadPackets(t);r.length>0&&(this.log(`Sending ${r.length} macro load packets to ${t}...`),r.forEach(c=>{this.network.sendToClient(t,"load",c)}));let h=s.getInputBindingsLoadPacket();h&&(this.network.sendToClient(t,"input-bindings",h),this.log(`Sent input bindings to ${t}`)),this.log(`User ${i} (${t}) joined`)}catch(i){this.log(`Failed to join: ${i}`),this.network.sendToClient(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}if(e.axes&&Object.entries(e.axes).forEach(([s,n])=>{i.setAxis(s,n)}),e.buttons&&Object.entries(e.buttons).forEach(([s,n])=>{i.setButton(s,n)}),e.mousePosition){let{x:s,y:n}=e.mousePosition;i.setMousePosition(s,n,!0)}e.textInputs&&Array.isArray(e.textInputs)&&i.setTextInputs(e.textInputs)}),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}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(),s.needsSendSounds()){let n=this.core.generateSoundLoadPackets();n.length>0&&(this.log(`Sending ${n.length} sound packets to ${s.id}...`),n.forEach(r=>{this.network.sendToClient(s.id,"sound-load",r)})),s.clearSendSounds()}}),this.core.endTick(),this.broadcastUpdates()}broadcastUpdates(){let t=this.core.endTickSplit(),e=this.core.getCurrentTick();t.forEach(({static:i,dynamic:s},n)=>{if(i&&(this.network.sendToClient(n,"update-static",i),console.warn(`[SERVER] update-static: ${i.length}B (tick ${e})`)),s){let h=this.network;h.sendToClientVolatile?h.sendToClientVolatile(n,"update-dynamic",s):this.network.sendToClient(n,"update-dynamic",s),console.warn(`[SERVER] update-dynamic: ${s.length}B (tick ${e})`)}let r=this.core.getUser(n);r&&r.hasBridgeMessages()&&r.getBridgeMessages().forEach(c=>{this.network.sendBridge(n,c.channel,c.data)})})}log(t){this.options.debug&&console.warn(`[ServerRuntime] ${t}`)}};g(l,"ServerRuntime");var p=l;export{p as ServerRuntime};
1
+ var g=Object.defineProperty;var d=(a,t,e)=>t in a?g(a,t,{enumerable:!0,configurable:!0,writable:!0,value:e}):a[t]=e;var l=(a,t)=>g(a,"name",{value:t,configurable:!0});var o=(a,t,e)=>(d(a,typeof t!="symbol"?t+"":t,e),e);import{Core as m}from"@utsp/core";import{SocketIOServer as k}from"@utsp/network-server";var u=class u{constructor(t){o(this,"core");o(this,"network");o(this,"options");o(this,"running",!1);o(this,"startTime",0);o(this,"lastTimestamp",0);o(this,"tickCount",0);o(this,"tps",0);o(this,"tpsUpdateTime",0);o(this,"tickInterval",null);o(this,"totalBytesSent",0);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,width:t.width??80,height:t.height??25,cors:t.cors??{origin:"*"}},this.log("Initializing ServerRuntime..."),this.core=new m({mode:"server",maxUsers:this.options.maxConnections}),this.network=new k({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.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})`);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.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(h=>{this.sendAndCount(t,"load",h)});let r=this.core.generateMacroLoadPackets(t);r.length>0&&(this.log(`Sending ${r.length} macro load packets to ${t}...`),r.forEach(h=>{this.sendAndCount(t,"load",h)}));let c=s.getInputBindingsLoadPacket();c&&(this.sendAndCount(t,"input-bindings",c),this.log(`Sent input bindings 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}if(e.axes&&Object.entries(e.axes).forEach(([s,n])=>{i.setAxis(s,n)}),e.buttons&&Object.entries(e.buttons).forEach(([s,n])=>{i.setButton(s,n)}),e.mousePosition){let{x:s,y:n}=e.mousePosition;i.setMousePosition(s,n,!0)}e.textInputs&&Array.isArray(e.textInputs)&&i.setTextInputs(e.textInputs)}),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}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(),s.needsSendSounds()){let n=this.core.generateSoundLoadPackets();n.length>0&&(this.log(`Sending ${n.length} sound packets to ${s.id}...`),n.forEach(r=>{this.sendAndCount(s.id,"sound-load",r)})),s.clearSendSounds()}}),this.core.endTick(),this.broadcastUpdates()}broadcastUpdates(){let t=this.core.endTickSplit(),e=this.core.getCurrentTick();t.forEach(({static:i,dynamic:s},n)=>{i&&(this.sendAndCount(n,"update-static",i),console.warn(`[SERVER] update-static: ${i.length}B (tick ${e})`)),s&&(this.sendVolatileAndCount(n,"update-dynamic",s),console.warn(`[SERVER] update-dynamic: ${s.length}B (tick ${e})`));let r=this.core.getUser(n);r&&r.hasBridgeMessages()&&r.getBridgeMessages().forEach(h=>{this.network.sendBridge(n,h.channel,h.data)})})}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}log(t){this.options.debug&&console.warn(`[ServerRuntime] ${t}`)}};l(u,"ServerRuntime");var p=u;export{p as ServerRuntime};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@utsp/runtime-server",
3
- "version": "0.12.0",
3
+ "version": "0.13.0-nightly.20251214163808.a699d8b",
4
4
  "description": "Server-side runtime for UTSP applications (Node.js, headless)",
5
5
  "author": "THP Software",
6
6
  "license": "MIT",
@@ -46,9 +46,9 @@
46
46
  "access": "public"
47
47
  },
48
48
  "dependencies": {
49
- "@utsp/core": "0.12.0",
50
- "@utsp/network-server": "0.12.0",
51
- "@utsp/types": "0.12.0"
49
+ "@utsp/core": "0.13.0-nightly.20251214163808.a699d8b",
50
+ "@utsp/network-server": "0.13.0-nightly.20251214163808.a699d8b",
51
+ "@utsp/types": "0.13.0-nightly.20251214163808.a699d8b"
52
52
  },
53
53
  "devDependencies": {
54
54
  "@types/node": "^20.0.0",