spoint 0.1.0 → 0.1.10
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/README.md +134 -209
- package/SKILL.md +95 -0
- package/apps/environment/index.js +200 -1
- package/apps/environment/models/decorative/.gitkeep +0 -0
- package/apps/environment/models/hazards/.gitkeep +0 -0
- package/apps/environment/models/interactive/.gitkeep +0 -0
- package/apps/environment/models/structures/.gitkeep +0 -0
- package/apps/environment/smartObjects.js +114 -0
- package/apps/interactable/index.js +155 -0
- package/apps/physics-crate/index.js +15 -9
- package/apps/power-crate/index.js +18 -12
- package/apps/tps-game/$GDUPI.vrm +0 -0
- package/apps/tps-game/Cleetus.vrm +0 -0
- package/apps/tps-game/index.js +185 -27
- package/apps/world/index.js +68 -22
- package/bin/create-app.js +337 -0
- package/client/ARControls.js +301 -0
- package/client/LoadingManager.js +117 -0
- package/client/MobileControls.js +1122 -0
- package/client/anim-lib.glb +0 -0
- package/client/animation.js +306 -0
- package/client/app.js +1341 -65
- package/client/camera.js +191 -33
- package/client/createLoadingScreen.js +69 -0
- package/client/editor/bridge.js +113 -0
- package/client/editor/css/main.css +794 -0
- package/client/editor/images/rotate.svg +4 -0
- package/client/editor/images/scale.svg +4 -0
- package/client/editor/images/translate.svg +4 -0
- package/client/editor/index.html +103 -0
- package/client/editor/js/Command.js +41 -0
- package/client/editor/js/Config.js +81 -0
- package/client/editor/js/Editor.js +785 -0
- package/client/editor/js/EditorControls.js +438 -0
- package/client/editor/js/History.js +321 -0
- package/client/editor/js/Loader.js +987 -0
- package/client/editor/js/LoaderUtils.js +90 -0
- package/client/editor/js/Menubar.Add.js +510 -0
- package/client/editor/js/Menubar.Edit.js +145 -0
- package/client/editor/js/Menubar.File.js +466 -0
- package/client/editor/js/Menubar.Help.js +73 -0
- package/client/editor/js/Menubar.Status.js +51 -0
- package/client/editor/js/Menubar.View.js +183 -0
- package/client/editor/js/Menubar.js +27 -0
- package/client/editor/js/Player.js +53 -0
- package/client/editor/js/Resizer.js +58 -0
- package/client/editor/js/Script.js +503 -0
- package/client/editor/js/Selector.js +102 -0
- package/client/editor/js/Sidebar.Geometry.BoxGeometry.js +121 -0
- package/client/editor/js/Sidebar.Geometry.BufferGeometry.js +115 -0
- package/client/editor/js/Sidebar.Geometry.CapsuleGeometry.js +97 -0
- package/client/editor/js/Sidebar.Geometry.CircleGeometry.js +97 -0
- package/client/editor/js/Sidebar.Geometry.CylinderGeometry.js +121 -0
- package/client/editor/js/Sidebar.Geometry.DodecahedronGeometry.js +73 -0
- package/client/editor/js/Sidebar.Geometry.ExtrudeGeometry.js +196 -0
- package/client/editor/js/Sidebar.Geometry.IcosahedronGeometry.js +73 -0
- package/client/editor/js/Sidebar.Geometry.LatheGeometry.js +98 -0
- package/client/editor/js/Sidebar.Geometry.Modifiers.js +73 -0
- package/client/editor/js/Sidebar.Geometry.OctahedronGeometry.js +74 -0
- package/client/editor/js/Sidebar.Geometry.PlaneGeometry.js +97 -0
- package/client/editor/js/Sidebar.Geometry.RingGeometry.js +121 -0
- package/client/editor/js/Sidebar.Geometry.ShapeGeometry.js +76 -0
- package/client/editor/js/Sidebar.Geometry.SphereGeometry.js +133 -0
- package/client/editor/js/Sidebar.Geometry.TetrahedronGeometry.js +74 -0
- package/client/editor/js/Sidebar.Geometry.TorusGeometry.js +109 -0
- package/client/editor/js/Sidebar.Geometry.TorusKnotGeometry.js +121 -0
- package/client/editor/js/Sidebar.Geometry.TubeGeometry.js +135 -0
- package/client/editor/js/Sidebar.Geometry.js +332 -0
- package/client/editor/js/Sidebar.Material.BooleanProperty.js +60 -0
- package/client/editor/js/Sidebar.Material.ColorProperty.js +87 -0
- package/client/editor/js/Sidebar.Material.ConstantProperty.js +62 -0
- package/client/editor/js/Sidebar.Material.MapProperty.js +249 -0
- package/client/editor/js/Sidebar.Material.NumberProperty.js +60 -0
- package/client/editor/js/Sidebar.Material.Program.js +73 -0
- package/client/editor/js/Sidebar.Material.RangeValueProperty.js +63 -0
- package/client/editor/js/Sidebar.Material.js +751 -0
- package/client/editor/js/Sidebar.Object.Animation.js +102 -0
- package/client/editor/js/Sidebar.Object.js +898 -0
- package/client/editor/js/Sidebar.Project.App.js +165 -0
- package/client/editor/js/Sidebar.Project.Image.js +225 -0
- package/client/editor/js/Sidebar.Project.Materials.js +82 -0
- package/client/editor/js/Sidebar.Project.Renderer.js +144 -0
- package/client/editor/js/Sidebar.Project.Video.js +242 -0
- package/client/editor/js/Sidebar.Project.js +31 -0
- package/client/editor/js/Sidebar.Properties.js +73 -0
- package/client/editor/js/Sidebar.Scene.js +585 -0
- package/client/editor/js/Sidebar.Script.js +129 -0
- package/client/editor/js/Sidebar.Settings.History.js +146 -0
- package/client/editor/js/Sidebar.Settings.Shortcuts.js +175 -0
- package/client/editor/js/Sidebar.Settings.js +60 -0
- package/client/editor/js/Sidebar.js +41 -0
- package/client/editor/js/Storage.js +98 -0
- package/client/editor/js/Strings.js +2028 -0
- package/client/editor/js/Toolbar.js +84 -0
- package/client/editor/js/Viewport.Controls.js +92 -0
- package/client/editor/js/Viewport.Info.js +136 -0
- package/client/editor/js/Viewport.Pathtracer.js +91 -0
- package/client/editor/js/Viewport.ViewHelper.js +39 -0
- package/client/editor/js/Viewport.XR.js +222 -0
- package/client/editor/js/Viewport.js +900 -0
- package/client/editor/js/commands/AddObjectCommand.js +68 -0
- package/client/editor/js/commands/AddScriptCommand.js +75 -0
- package/client/editor/js/commands/Commands.js +23 -0
- package/client/editor/js/commands/MoveObjectCommand.js +111 -0
- package/client/editor/js/commands/MultiCmdsCommand.js +85 -0
- package/client/editor/js/commands/RemoveObjectCommand.js +88 -0
- package/client/editor/js/commands/RemoveScriptCommand.js +81 -0
- package/client/editor/js/commands/SetColorCommand.js +73 -0
- package/client/editor/js/commands/SetGeometryCommand.js +87 -0
- package/client/editor/js/commands/SetGeometryValueCommand.js +70 -0
- package/client/editor/js/commands/SetMaterialColorCommand.js +86 -0
- package/client/editor/js/commands/SetMaterialCommand.js +79 -0
- package/client/editor/js/commands/SetMaterialMapCommand.js +143 -0
- package/client/editor/js/commands/SetMaterialRangeCommand.js +91 -0
- package/client/editor/js/commands/SetMaterialValueCommand.js +90 -0
- package/client/editor/js/commands/SetMaterialVectorCommand.js +79 -0
- package/client/editor/js/commands/SetPositionCommand.js +84 -0
- package/client/editor/js/commands/SetRotationCommand.js +84 -0
- package/client/editor/js/commands/SetScaleCommand.js +84 -0
- package/client/editor/js/commands/SetSceneCommand.js +103 -0
- package/client/editor/js/commands/SetScriptValueCommand.js +80 -0
- package/client/editor/js/commands/SetShadowValueCommand.js +73 -0
- package/client/editor/js/commands/SetUuidCommand.js +70 -0
- package/client/editor/js/commands/SetValueCommand.js +75 -0
- package/client/editor/js/libs/acorn/acorn.js +3236 -0
- package/client/editor/js/libs/acorn/acorn_loose.js +1299 -0
- package/client/editor/js/libs/acorn/walk.js +344 -0
- package/client/editor/js/libs/app/index.html +57 -0
- package/client/editor/js/libs/app.js +251 -0
- package/client/editor/js/libs/codemirror/addon/dialog.css +32 -0
- package/client/editor/js/libs/codemirror/addon/dialog.js +163 -0
- package/client/editor/js/libs/codemirror/addon/show-hint.css +36 -0
- package/client/editor/js/libs/codemirror/addon/show-hint.js +529 -0
- package/client/editor/js/libs/codemirror/addon/tern.css +87 -0
- package/client/editor/js/libs/codemirror/addon/tern.js +750 -0
- package/client/editor/js/libs/codemirror/codemirror.css +344 -0
- package/client/editor/js/libs/codemirror/codemirror.js +9849 -0
- package/client/editor/js/libs/codemirror/mode/glsl.js +233 -0
- package/client/editor/js/libs/codemirror/mode/javascript.js +959 -0
- package/client/editor/js/libs/codemirror/theme/monokai.css +41 -0
- package/client/editor/js/libs/esprima.js +6401 -0
- package/client/editor/js/libs/jsonlint.js +453 -0
- package/client/editor/js/libs/signals.min.js +14 -0
- package/client/editor/js/libs/tern-threejs/threejs.js +5031 -0
- package/client/editor/js/libs/ternjs/comment.js +87 -0
- package/client/editor/js/libs/ternjs/def.js +588 -0
- package/client/editor/js/libs/ternjs/doc_comment.js +401 -0
- package/client/editor/js/libs/ternjs/infer.js +1635 -0
- package/client/editor/js/libs/ternjs/polyfill.js +80 -0
- package/client/editor/js/libs/ternjs/signal.js +26 -0
- package/client/editor/js/libs/ternjs/tern.js +993 -0
- package/client/editor/js/libs/ui.js +1346 -0
- package/client/editor/js/libs/ui.three.js +855 -0
- package/client/facial-animation.js +455 -0
- package/client/index.html +7 -4
- package/client/loading.css +147 -0
- package/client/loading.html +25 -0
- package/client/style.css +251 -0
- package/package.json +7 -3
- package/server.js +9 -1
- package/src/apps/AppContext.js +1 -1
- package/src/apps/AppLoader.js +50 -37
- package/src/apps/AppRuntime.js +32 -8
- package/src/client/InputHandler.js +233 -0
- package/src/client/JitterBuffer.js +207 -0
- package/src/client/KalmanFilter.js +125 -0
- package/src/client/MessageHandler.js +101 -0
- package/src/client/PhysicsNetworkClient.js +141 -68
- package/src/client/ReconnectManager.js +62 -0
- package/src/client/SmoothInterpolation.js +127 -0
- package/src/client/SnapshotProcessor.js +144 -0
- package/src/connection/ConnectionManager.js +21 -3
- package/src/connection/SessionStore.js +13 -3
- package/src/index.client.js +4 -6
- package/src/netcode/EventLog.js +29 -15
- package/src/netcode/LagCompensator.js +25 -26
- package/src/netcode/NetworkState.js +4 -1
- package/src/netcode/PhysicsIntegration.js +20 -6
- package/src/netcode/PlayerManager.js +10 -2
- package/src/netcode/SnapshotEncoder.js +66 -19
- package/src/netcode/TickSystem.js +13 -4
- package/src/physics/World.js +66 -13
- package/src/protocol/msgpack.js +90 -63
- package/src/sdk/ReloadHandlers.js +12 -2
- package/src/sdk/ReloadManager.js +5 -0
- package/src/sdk/ServerHandlers.js +50 -11
- package/src/sdk/StaticHandler.js +22 -6
- package/src/sdk/TickHandler.js +101 -34
- package/src/sdk/scaffold.js +28 -0
- package/src/sdk/server.js +59 -33
- package/src/shared/movement.js +2 -1
- package/src/spatial/Octree.js +5 -0
- package/apps/interactive-door/index.js +0 -33
- package/apps/patrol-npc/index.js +0 -37
- package/src/connection/QualityMonitor.js +0 -46
- package/src/debug/StateInspector.js +0 -42
- package/src/index.js +0 -1
- package/src/index.server.js +0 -27
- package/src/protocol/Codec.js +0 -60
- package/src/protocol/SequenceTracker.js +0 -71
- package/src/sdk/ClientMessageHandler.js +0 -80
- package/src/sdk/client.js +0 -122
- package/world/kaira.glb +0 -0
- package/world/schwust.glb +0 -0
package/src/protocol/msgpack.js
CHANGED
|
@@ -1,74 +1,101 @@
|
|
|
1
1
|
const encoder = new TextEncoder()
|
|
2
2
|
const decoder = new TextDecoder()
|
|
3
|
+
const f64Buf = new ArrayBuffer(8)
|
|
4
|
+
const f64View = new DataView(f64Buf)
|
|
5
|
+
const f64Bytes = new Uint8Array(f64Buf)
|
|
3
6
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
7
|
+
let buf = new Uint8Array(4096)
|
|
8
|
+
let pos = 0
|
|
9
|
+
|
|
10
|
+
function grow(need) {
|
|
11
|
+
if (pos + need <= buf.length) return
|
|
12
|
+
let size = buf.length
|
|
13
|
+
while (size < pos + need) size *= 2
|
|
14
|
+
const next = new Uint8Array(size)
|
|
15
|
+
next.set(buf.subarray(0, pos))
|
|
16
|
+
buf = next
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function w1(v) { grow(1); buf[pos++] = v }
|
|
20
|
+
function w2(a, b) { grow(2); buf[pos++] = a; buf[pos++] = b }
|
|
21
|
+
function w3(a, b, c) { grow(3); buf[pos++] = a; buf[pos++] = b; buf[pos++] = c }
|
|
22
|
+
function w5(a, b, c, d, e) { grow(5); buf[pos++] = a; buf[pos++] = b; buf[pos++] = c; buf[pos++] = d; buf[pos++] = e }
|
|
23
|
+
|
|
24
|
+
function writeFloat64(value) {
|
|
25
|
+
grow(9)
|
|
26
|
+
buf[pos++] = 0xcb
|
|
27
|
+
f64View.setFloat64(0, value, false)
|
|
28
|
+
buf[pos++] = f64Bytes[0]; buf[pos++] = f64Bytes[1]; buf[pos++] = f64Bytes[2]; buf[pos++] = f64Bytes[3]
|
|
29
|
+
buf[pos++] = f64Bytes[4]; buf[pos++] = f64Bytes[5]; buf[pos++] = f64Bytes[6]; buf[pos++] = f64Bytes[7]
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function write(value) {
|
|
33
|
+
if (value === null || value === undefined) {
|
|
34
|
+
w1(0xc0)
|
|
35
|
+
} else if (value === false) {
|
|
36
|
+
w1(0xc2)
|
|
37
|
+
} else if (value === true) {
|
|
38
|
+
w1(0xc3)
|
|
39
|
+
} else if (typeof value === 'number') {
|
|
40
|
+
if (Number.isInteger(value)) {
|
|
41
|
+
if (value >= 0) {
|
|
42
|
+
if (value < 128) w1(value)
|
|
43
|
+
else if (value < 256) w2(0xcc, value)
|
|
44
|
+
else if (value < 65536) w3(0xcd, value >> 8, value & 0xff)
|
|
45
|
+
else if (value < 4294967296) w5(0xce, (value >> 24) & 0xff, (value >> 16) & 0xff, (value >> 8) & 0xff, value & 0xff)
|
|
46
|
+
else writeFloat64(value)
|
|
28
47
|
} else {
|
|
29
|
-
|
|
48
|
+
if (value >= -32) w1(value & 0xff)
|
|
49
|
+
else if (value >= -128) w2(0xd0, value & 0xff)
|
|
50
|
+
else if (value >= -32768) w3(0xd1, (value >> 8) & 0xff, value & 0xff)
|
|
51
|
+
else if (value >= -2147483648) w5(0xd2, (value >> 24) & 0xff, (value >> 16) & 0xff, (value >> 8) & 0xff, value & 0xff)
|
|
52
|
+
else writeFloat64(value)
|
|
30
53
|
}
|
|
31
|
-
} else
|
|
32
|
-
|
|
33
|
-
const len = bytes.length
|
|
34
|
-
if (len < 32) chunks.push(0xa0 | len)
|
|
35
|
-
else if (len < 256) chunks.push(0xd9, len)
|
|
36
|
-
else if (len < 65536) chunks.push(0xda, len >> 8, len & 0xff)
|
|
37
|
-
else chunks.push(0xdb, (len >> 24) & 0xff, (len >> 16) & 0xff, (len >> 8) & 0xff, len & 0xff)
|
|
38
|
-
for (let i = 0; i < len; i++) chunks.push(bytes[i])
|
|
39
|
-
} else if (value instanceof Uint8Array || value instanceof ArrayBuffer) {
|
|
40
|
-
const bytes = value instanceof ArrayBuffer ? new Uint8Array(value) : value
|
|
41
|
-
const len = bytes.length
|
|
42
|
-
if (len < 256) chunks.push(0xc4, len)
|
|
43
|
-
else if (len < 65536) chunks.push(0xc5, len >> 8, len & 0xff)
|
|
44
|
-
else chunks.push(0xc6, (len >> 24) & 0xff, (len >> 16) & 0xff, (len >> 8) & 0xff, len & 0xff)
|
|
45
|
-
for (let i = 0; i < len; i++) chunks.push(bytes[i])
|
|
46
|
-
} else if (Array.isArray(value)) {
|
|
47
|
-
const len = value.length
|
|
48
|
-
if (len < 16) chunks.push(0x90 | len)
|
|
49
|
-
else if (len < 65536) chunks.push(0xdc, len >> 8, len & 0xff)
|
|
50
|
-
else chunks.push(0xdd, (len >> 24) & 0xff, (len >> 16) & 0xff, (len >> 8) & 0xff, len & 0xff)
|
|
51
|
-
for (let i = 0; i < len; i++) write(value[i])
|
|
52
|
-
} else if (typeof value === 'object') {
|
|
53
|
-
const keys = Object.keys(value)
|
|
54
|
-
const len = keys.length
|
|
55
|
-
if (len < 16) chunks.push(0x80 | len)
|
|
56
|
-
else if (len < 65536) chunks.push(0xde, len >> 8, len & 0xff)
|
|
57
|
-
else chunks.push(0xdf, (len >> 24) & 0xff, (len >> 16) & 0xff, (len >> 8) & 0xff, len & 0xff)
|
|
58
|
-
for (const key of keys) { write(key); write(value[key]) }
|
|
54
|
+
} else {
|
|
55
|
+
writeFloat64(value)
|
|
59
56
|
}
|
|
57
|
+
} else if (typeof value === 'string') {
|
|
58
|
+
const bytes = encoder.encode(value)
|
|
59
|
+
const len = bytes.length
|
|
60
|
+
if (len < 32) w1(0xa0 | len)
|
|
61
|
+
else if (len < 256) w2(0xd9, len)
|
|
62
|
+
else if (len < 65536) w3(0xda, len >> 8, len & 0xff)
|
|
63
|
+
else w5(0xdb, (len >> 24) & 0xff, (len >> 16) & 0xff, (len >> 8) & 0xff, len & 0xff)
|
|
64
|
+
grow(len)
|
|
65
|
+
buf.set(bytes, pos)
|
|
66
|
+
pos += len
|
|
67
|
+
} else if (value instanceof Uint8Array || value instanceof ArrayBuffer) {
|
|
68
|
+
const bytes = value instanceof ArrayBuffer ? new Uint8Array(value) : value
|
|
69
|
+
const len = bytes.length
|
|
70
|
+
if (len < 256) w2(0xc4, len)
|
|
71
|
+
else if (len < 65536) w3(0xc5, len >> 8, len & 0xff)
|
|
72
|
+
else w5(0xc6, (len >> 24) & 0xff, (len >> 16) & 0xff, (len >> 8) & 0xff, len & 0xff)
|
|
73
|
+
grow(len)
|
|
74
|
+
buf.set(bytes, pos)
|
|
75
|
+
pos += len
|
|
76
|
+
} else if (Array.isArray(value)) {
|
|
77
|
+
const len = value.length
|
|
78
|
+
if (len < 16) w1(0x90 | len)
|
|
79
|
+
else if (len < 65536) w3(0xdc, len >> 8, len & 0xff)
|
|
80
|
+
else w5(0xdd, (len >> 24) & 0xff, (len >> 16) & 0xff, (len >> 8) & 0xff, len & 0xff)
|
|
81
|
+
for (let i = 0; i < len; i++) write(value[i])
|
|
82
|
+
} else if (typeof value === 'object') {
|
|
83
|
+
const keys = Object.keys(value)
|
|
84
|
+
const len = keys.length
|
|
85
|
+
if (len < 16) w1(0x80 | len)
|
|
86
|
+
else if (len < 65536) w3(0xde, len >> 8, len & 0xff)
|
|
87
|
+
else w5(0xdf, (len >> 24) & 0xff, (len >> 16) & 0xff, (len >> 8) & 0xff, len & 0xff)
|
|
88
|
+
for (const key of keys) { write(key); write(value[key]) }
|
|
60
89
|
}
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
const bytes = new Uint8Array(buf)
|
|
66
|
-
for (let i = 0; i < 8; i++) chunks.push(bytes[i])
|
|
67
|
-
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export function pack(value) {
|
|
93
|
+
pos = 0
|
|
68
94
|
write(value)
|
|
69
|
-
return
|
|
95
|
+
return buf.slice(0, pos)
|
|
70
96
|
}
|
|
71
97
|
|
|
98
|
+
|
|
72
99
|
export function unpack(buffer) {
|
|
73
100
|
const bytes = buffer instanceof Uint8Array ? buffer : new Uint8Array(buffer)
|
|
74
101
|
let offset = 0
|
|
@@ -109,8 +136,8 @@ export function unpack(buffer) {
|
|
|
109
136
|
function readInt8() { const v = bytes[offset++]; return v > 127 ? v - 256 : v }
|
|
110
137
|
function readInt16() { const v = (bytes[offset] << 8) | bytes[offset + 1]; offset += 2; return v > 32767 ? v - 65536 : v }
|
|
111
138
|
function readInt32() { const v = (bytes[offset] << 24) | (bytes[offset + 1] << 16) | (bytes[offset + 2] << 8) | bytes[offset + 3]; offset += 4; return v }
|
|
112
|
-
function readFloat32() { const
|
|
113
|
-
function readFloat64() { const
|
|
139
|
+
function readFloat32() { const b = new ArrayBuffer(4); const v = new Uint8Array(b); for (let i = 0; i < 4; i++) v[i] = bytes[offset++]; return new DataView(b).getFloat32(0, false) }
|
|
140
|
+
function readFloat64() { const b = new ArrayBuffer(8); const v = new Uint8Array(b); for (let i = 0; i < 8; i++) v[i] = bytes[offset++]; return new DataView(b).getFloat64(0, false) }
|
|
114
141
|
function readString(len) { const slice = bytes.subarray(offset, offset + len); offset += len; return decoder.decode(slice) }
|
|
115
142
|
function readBin(len) { const slice = bytes.slice(offset, offset + len); offset += len; return slice }
|
|
116
143
|
function readArray(len) { const arr = new Array(len); for (let i = 0; i < len; i++) arr[i] = read(); return arr }
|
|
@@ -34,8 +34,18 @@ export function createReloadHandlers(deps) {
|
|
|
34
34
|
} = deps
|
|
35
35
|
|
|
36
36
|
const reloadTickHandler = async () => {
|
|
37
|
-
const
|
|
38
|
-
|
|
37
|
+
const t = Date.now()
|
|
38
|
+
const { applyMovement, DEFAULT_MOVEMENT } = await import('../shared/movement.js?' + t)
|
|
39
|
+
const { createTickHandler: refreshHandler } = await import('./TickHandler.js?' + t)
|
|
40
|
+
let movement = deps.movement
|
|
41
|
+
if (deps.worldConfigPath) {
|
|
42
|
+
try {
|
|
43
|
+
const wm = await import(deps.worldConfigPath + '?' + t)
|
|
44
|
+
const wd = wm.default || wm
|
|
45
|
+
if (wd.movement) movement = wd.movement
|
|
46
|
+
} catch (e) {}
|
|
47
|
+
}
|
|
48
|
+
return refreshHandler({ ...deps, movement, _movement: { applyMovement, DEFAULT_MOVEMENT } })
|
|
39
49
|
}
|
|
40
50
|
|
|
41
51
|
const reloadPhysicsIntegration = async () => {
|
package/src/sdk/ReloadManager.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { watch } from 'node:fs/promises'
|
|
2
|
+
import { existsSync } from 'node:fs'
|
|
2
3
|
import { resolve } from 'node:path'
|
|
3
4
|
|
|
4
5
|
export class ReloadManager {
|
|
@@ -16,6 +17,10 @@ export class ReloadManager {
|
|
|
16
17
|
addWatcher(moduleId, filePath, onReload, validator) {
|
|
17
18
|
const absPath = resolve(filePath)
|
|
18
19
|
if (this._watchers.has(moduleId)) return
|
|
20
|
+
if (!existsSync(absPath)) {
|
|
21
|
+
console.debug(`[ReloadManager] skipping watch for missing file: ${moduleId}`)
|
|
22
|
+
return
|
|
23
|
+
}
|
|
19
24
|
this._reloadState.set(moduleId, { inProgress: false, lastSuccess: null, failureCount: 0 })
|
|
20
25
|
this._failureCounters.set(moduleId, 0)
|
|
21
26
|
if (validator) this._validators.set(moduleId, validator)
|
|
@@ -6,9 +6,10 @@ export function createConnectionHandlers(ctx) {
|
|
|
6
6
|
|
|
7
7
|
function onClientConnect(transport) {
|
|
8
8
|
const sp = [...ctx.worldSpawnPoint]
|
|
9
|
-
const
|
|
9
|
+
const playerConfig = ctx.currentWorldDef?.player || {}
|
|
10
|
+
const playerId = playerManager.addPlayer(transport, { position: sp, health: playerConfig.health })
|
|
10
11
|
networkState.addPlayer(playerId, { position: sp })
|
|
11
|
-
physicsIntegration.addPlayerCollider(playerId, 0.4)
|
|
12
|
+
physicsIntegration.addPlayerCollider(playerId, playerConfig.capsuleRadius || 0.4)
|
|
12
13
|
physicsIntegration.setPlayerPosition(playerId, sp)
|
|
13
14
|
const playerState = playerManager.getPlayer(playerId).state
|
|
14
15
|
lagCompensator.recordPlayerPosition(playerId, playerState.position, playerState.rotation, playerState.velocity, tickSystem.currentTick)
|
|
@@ -24,23 +25,25 @@ export function createConnectionHandlers(ctx) {
|
|
|
24
25
|
}
|
|
25
26
|
const snap = appRuntime.getSnapshot()
|
|
26
27
|
connections.send(playerId, MSG.SNAPSHOT, { seq: ++ctx.snapshotSeq, ...SnapshotEncoder.encode(snap) })
|
|
27
|
-
appRuntime.fireMessage(
|
|
28
|
+
for (const [entityId] of appRuntime.apps) appRuntime.fireMessage(entityId, { type: 'player_join', playerId })
|
|
28
29
|
emitter.emit('playerJoin', { id: playerId })
|
|
29
30
|
}
|
|
30
31
|
|
|
31
32
|
connections.on('message', (clientId, msg) => {
|
|
32
33
|
if (inspector.handleMessage(clientId, msg)) return
|
|
34
|
+
if (msg.type === MSG.HEARTBEAT) {
|
|
35
|
+
connections.send(clientId, MSG.HEARTBEAT_ACK, { timestamp: msg.payload?.timestamp || Date.now() })
|
|
36
|
+
return
|
|
37
|
+
}
|
|
33
38
|
if (msg.type === MSG.INPUT || msg.type === MSG.PLAYER_INPUT) {
|
|
34
39
|
playerManager.addInput(clientId, msg.payload?.input || msg.payload)
|
|
35
40
|
return
|
|
36
41
|
}
|
|
37
42
|
if (msg.type === MSG.APP_EVENT) {
|
|
38
43
|
if (msg.payload?.entityId) appRuntime.fireInteract(msg.payload.entityId, { id: clientId })
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
const origin = [pos[0], pos[1] + 0.9, pos[2]]
|
|
43
|
-
appRuntime.fireMessage('game', { ...msg.payload, shooterId: clientId, origin })
|
|
44
|
+
const eventData = { ...msg.payload, senderId: clientId }
|
|
45
|
+
for (const [entityId] of appRuntime.apps) {
|
|
46
|
+
appRuntime.fireMessage(entityId, eventData)
|
|
44
47
|
}
|
|
45
48
|
return
|
|
46
49
|
}
|
|
@@ -50,10 +53,46 @@ export function createConnectionHandlers(ctx) {
|
|
|
50
53
|
connections.send(clientId, MSG.DISCONNECT_REASON, { code: DISCONNECT_REASONS.INVALID_SESSION })
|
|
51
54
|
return
|
|
52
55
|
}
|
|
56
|
+
const oldId = session.playerId
|
|
57
|
+
const savedState = session.state || {}
|
|
58
|
+
const client = connections.getClient(clientId)
|
|
59
|
+
const transport = client?.transport
|
|
60
|
+
if (!transport) return
|
|
61
|
+
const playerConfig = ctx.currentWorldDef?.player || {}
|
|
62
|
+
const sp = savedState.position || [...ctx.worldSpawnPoint]
|
|
63
|
+
if (playerManager.getPlayer(oldId)) {
|
|
64
|
+
playerManager.removePlayer(oldId)
|
|
65
|
+
networkState.removePlayer(oldId)
|
|
66
|
+
physicsIntegration.removePlayerCollider(oldId)
|
|
67
|
+
lagCompensator.clearPlayerHistory(oldId)
|
|
68
|
+
connections.broadcast(MSG.PLAYER_LEAVE, { playerId: oldId })
|
|
69
|
+
}
|
|
70
|
+
if (clientId !== oldId && playerManager.getPlayer(clientId)) {
|
|
71
|
+
playerManager.removePlayer(clientId)
|
|
72
|
+
networkState.removePlayer(clientId)
|
|
73
|
+
physicsIntegration.removePlayerCollider(clientId)
|
|
74
|
+
lagCompensator.clearPlayerHistory(clientId)
|
|
75
|
+
connections.broadcast(MSG.PLAYER_LEAVE, { playerId: clientId })
|
|
76
|
+
}
|
|
77
|
+
connections.detachClient(clientId)
|
|
78
|
+
const newId = playerManager.addPlayer(transport, { position: sp, health: savedState.health ?? playerConfig.health ?? 100, velocity: savedState.velocity, rotation: savedState.rotation })
|
|
79
|
+
networkState.addPlayer(newId, { position: sp })
|
|
80
|
+
physicsIntegration.addPlayerCollider(newId, playerConfig.capsuleRadius || 0.4)
|
|
81
|
+
physicsIntegration.setPlayerPosition(newId, sp)
|
|
82
|
+
const reconnClient = connections.addClient(newId, transport)
|
|
83
|
+
reconnClient.sessionToken = msg.payload.sessionToken
|
|
84
|
+
sessions.update(msg.payload.sessionToken, { state: playerManager.getPlayer(newId).state })
|
|
85
|
+
connections.send(newId, MSG.RECONNECT_ACK, { playerId: newId, tick: tickSystem.currentTick, sessionToken: msg.payload.sessionToken })
|
|
86
|
+
if (ctx.currentWorldDef) connections.send(newId, MSG.WORLD_DEF, ctx.currentWorldDef)
|
|
87
|
+
const clientModules = appLoader.getClientModules()
|
|
88
|
+
for (const [appName, code] of Object.entries(clientModules)) {
|
|
89
|
+
connections.send(newId, MSG.APP_MODULE, { app: appName, code })
|
|
90
|
+
}
|
|
53
91
|
const snap = networkState.getSnapshot()
|
|
54
92
|
const ents = appRuntime.getSnapshot()
|
|
55
|
-
connections.send(
|
|
56
|
-
|
|
93
|
+
connections.send(newId, MSG.STATE_RECOVERY, { snapshot: SnapshotEncoder.encode({ tick: snap.tick, timestamp: snap.timestamp, players: snap.players, entities: ents.entities }), tick: tickSystem.currentTick })
|
|
94
|
+
for (const [entityId] of appRuntime.apps) appRuntime.fireMessage(entityId, { type: 'player_join', playerId: newId })
|
|
95
|
+
emitter.emit('playerJoin', { id: newId, reconnected: true })
|
|
57
96
|
return
|
|
58
97
|
}
|
|
59
98
|
emitter.emit('message', clientId, msg)
|
|
@@ -62,7 +101,7 @@ export function createConnectionHandlers(ctx) {
|
|
|
62
101
|
connections.on('disconnect', (clientId, reason) => {
|
|
63
102
|
const client = connections.getClient(clientId)
|
|
64
103
|
if (client?.sessionToken) { const p = playerManager.getPlayer(clientId); if (p) sessions.update(client.sessionToken, { state: p.state }) }
|
|
65
|
-
appRuntime.fireMessage(
|
|
104
|
+
for (const [entityId] of appRuntime.apps) appRuntime.fireMessage(entityId, { type: 'player_leave', playerId: clientId })
|
|
66
105
|
physicsIntegration.removePlayerCollider(clientId)
|
|
67
106
|
lagCompensator.clearPlayerHistory(clientId)
|
|
68
107
|
inspector.removeClient(clientId)
|
package/src/sdk/StaticHandler.js
CHANGED
|
@@ -1,32 +1,48 @@
|
|
|
1
|
-
import { readFileSync, existsSync } from 'node:fs'
|
|
1
|
+
import { readFileSync, existsSync, statSync } from 'node:fs'
|
|
2
2
|
import { join, extname } from 'node:path'
|
|
3
|
+
import { gzipSync } from 'node:zlib'
|
|
3
4
|
|
|
4
5
|
const MIME_TYPES = {
|
|
5
6
|
'.html': 'text/html', '.js': 'text/javascript', '.css': 'text/css',
|
|
6
|
-
'.json': 'application/json', '.glb': 'model/gltf-binary', '.gltf': 'model/gltf+json',
|
|
7
|
+
'.json': 'application/json', '.glb': 'model/gltf-binary', '.gltf': 'model/gltf+json', '.vrm': 'model/gltf-binary',
|
|
7
8
|
'.png': 'image/png', '.jpg': 'image/jpeg', '.webp': 'image/webp',
|
|
8
|
-
'.svg': 'image/svg+xml', '.wasm': 'application/wasm'
|
|
9
|
+
'.svg': 'image/svg+xml', '.wasm': 'application/wasm', '.ico': 'image/x-icon'
|
|
9
10
|
}
|
|
10
11
|
|
|
12
|
+
const GZIP_EXTENSIONS = new Set(['.glb', '.vrm', '.gltf', '.js', '.css', '.html', '.json'])
|
|
13
|
+
|
|
11
14
|
export function createStaticHandler(dirs) {
|
|
12
15
|
return (req, res) => {
|
|
13
16
|
const url = req.url.split('?')[0]
|
|
17
|
+
if (url === '/favicon.ico') {
|
|
18
|
+
res.writeHead(204)
|
|
19
|
+
res.end()
|
|
20
|
+
return
|
|
21
|
+
}
|
|
14
22
|
for (const { prefix, dir } of dirs) {
|
|
15
23
|
if (!url.startsWith(prefix)) continue
|
|
16
24
|
const relative = url === prefix ? '/index.html' : url.slice(prefix.length)
|
|
17
25
|
const fp = join(dir, relative)
|
|
18
|
-
if (existsSync(fp)) {
|
|
26
|
+
if (existsSync(fp) && statSync(fp).isFile()) {
|
|
19
27
|
const ext = extname(fp)
|
|
20
28
|
const headers = { 'Content-Type': MIME_TYPES[ext] || 'application/octet-stream' }
|
|
21
29
|
if (ext === '.js' || ext === '.html' || ext === '.css') {
|
|
22
30
|
headers['Cache-Control'] = 'no-cache, no-store, must-revalidate'
|
|
31
|
+
} else if (ext === '.glb' || ext === '.vrm' || ext === '.gltf') {
|
|
32
|
+
headers['Cache-Control'] = 'public, max-age=86400, immutable'
|
|
33
|
+
}
|
|
34
|
+
let content = readFileSync(fp)
|
|
35
|
+
if (GZIP_EXTENSIONS.has(ext) && content.length > 100) {
|
|
36
|
+
content = gzipSync(content)
|
|
37
|
+
headers['Content-Encoding'] = 'gzip'
|
|
23
38
|
}
|
|
39
|
+
headers['Content-Length'] = content.length
|
|
24
40
|
res.writeHead(200, headers)
|
|
25
|
-
res.end(
|
|
41
|
+
res.end(content)
|
|
26
42
|
return
|
|
27
43
|
}
|
|
28
44
|
}
|
|
29
|
-
res.writeHead(404)
|
|
45
|
+
res.writeHead(404, { 'Cache-Control': 'no-store' })
|
|
30
46
|
res.end('not found')
|
|
31
47
|
}
|
|
32
48
|
}
|
package/src/sdk/TickHandler.js
CHANGED
|
@@ -1,84 +1,151 @@
|
|
|
1
1
|
import { MSG } from '../protocol/MessageTypes.js'
|
|
2
2
|
import { SnapshotEncoder } from '../netcode/SnapshotEncoder.js'
|
|
3
|
-
import { applyMovement, DEFAULT_MOVEMENT } from '../shared/movement.js'
|
|
3
|
+
import { applyMovement as _applyMovement, DEFAULT_MOVEMENT as _DEFAULT_MOVEMENT } from '../shared/movement.js'
|
|
4
|
+
|
|
5
|
+
const KEYFRAME_INTERVAL = 128
|
|
4
6
|
|
|
5
7
|
export function createTickHandler(deps) {
|
|
6
8
|
const {
|
|
7
9
|
networkState, playerManager, physicsIntegration,
|
|
8
10
|
lagCompensator, physics, appRuntime, connections,
|
|
9
|
-
movement: m = {}, stageLoader, eventLog
|
|
11
|
+
movement: m = {}, stageLoader, eventLog, _movement
|
|
10
12
|
} = deps
|
|
13
|
+
const applyMovement = _movement?.applyMovement || _applyMovement
|
|
14
|
+
const DEFAULT_MOVEMENT = _movement?.DEFAULT_MOVEMENT || _DEFAULT_MOVEMENT
|
|
11
15
|
const movement = { ...DEFAULT_MOVEMENT, ...m }
|
|
12
|
-
const collisionRestitution =
|
|
13
|
-
const collisionDamping =
|
|
16
|
+
const collisionRestitution = movement.collisionRestitution || 0.2
|
|
17
|
+
const collisionDamping = movement.collisionDamping || 0.25
|
|
14
18
|
let snapshotSeq = 0
|
|
15
19
|
|
|
20
|
+
const playerEntityMaps = new Map()
|
|
21
|
+
let broadcastEntityMap = new Map()
|
|
22
|
+
|
|
23
|
+
let profileLog = 0
|
|
24
|
+
const separated = new Set()
|
|
16
25
|
return function onTick(tick, dt) {
|
|
26
|
+
const t0 = performance.now()
|
|
17
27
|
networkState.setTick(tick, Date.now())
|
|
18
|
-
|
|
28
|
+
const players = playerManager.getConnectedPlayers()
|
|
29
|
+
for (const player of players) {
|
|
19
30
|
const inputs = playerManager.getInputs(player.id)
|
|
20
31
|
const st = player.state
|
|
21
|
-
let inp = null
|
|
22
32
|
|
|
23
33
|
if (inputs.length > 0) {
|
|
24
|
-
|
|
25
|
-
if (inp) {
|
|
26
|
-
const yaw = inp.yaw || 0
|
|
27
|
-
st.rotation = [0, Math.sin(yaw / 2), 0, Math.cos(yaw / 2)]
|
|
28
|
-
}
|
|
34
|
+
player.lastInput = inputs[inputs.length - 1].data
|
|
29
35
|
playerManager.clearInputs(player.id)
|
|
30
36
|
}
|
|
37
|
+
const inp = player.lastInput || null
|
|
38
|
+
if (inp) {
|
|
39
|
+
const yaw = inp.yaw || 0
|
|
40
|
+
st.rotation = [0, Math.sin(yaw / 2), 0, Math.cos(yaw / 2)]
|
|
41
|
+
st.crouch = inp.crouch ? 1 : 0
|
|
42
|
+
st.lookPitch = inp.pitch || 0
|
|
43
|
+
st.lookYaw = yaw
|
|
44
|
+
}
|
|
31
45
|
|
|
32
46
|
applyMovement(st, inp, movement, dt)
|
|
47
|
+
if (inp) physicsIntegration.setCrouch(player.id, !!inp.crouch)
|
|
48
|
+
const wishedVx = st.velocity[0], wishedVz = st.velocity[2]
|
|
33
49
|
const updated = physicsIntegration.updatePlayerPhysics(player.id, st, dt)
|
|
34
50
|
st.position = updated.position
|
|
35
51
|
st.velocity = updated.velocity
|
|
52
|
+
st.velocity[0] = wishedVx
|
|
53
|
+
st.velocity[2] = wishedVz
|
|
36
54
|
st.onGround = updated.onGround
|
|
37
55
|
lagCompensator.recordPlayerPosition(player.id, st.position, st.rotation, st.velocity, tick)
|
|
38
56
|
networkState.updatePlayer(player.id, {
|
|
39
57
|
position: st.position, rotation: st.rotation,
|
|
40
58
|
velocity: st.velocity, onGround: st.onGround,
|
|
41
|
-
health: st.health, inputSequence: player.inputSequence
|
|
59
|
+
health: st.health, inputSequence: player.inputSequence,
|
|
60
|
+
crouch: st.crouch || 0, lookPitch: st.lookPitch || 0, lookYaw: st.lookYaw || 0
|
|
42
61
|
})
|
|
43
62
|
}
|
|
44
|
-
const
|
|
63
|
+
const t1 = performance.now()
|
|
64
|
+
separated.clear()
|
|
45
65
|
for (const player of players) {
|
|
46
66
|
const collisions = physicsIntegration.checkCollisionWithOthers(player.id, players)
|
|
47
67
|
for (const collision of collisions) {
|
|
68
|
+
const pairKey = player.id < collision.playerId ? `${player.id}-${collision.playerId}` : `${collision.playerId}-${player.id}`
|
|
69
|
+
if (separated.has(pairKey)) continue
|
|
70
|
+
separated.add(pairKey)
|
|
48
71
|
const other = playerManager.getPlayer(collision.playerId)
|
|
49
72
|
if (!other) continue
|
|
50
|
-
const
|
|
51
|
-
const
|
|
52
|
-
const
|
|
53
|
-
const
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
player.state.
|
|
57
|
-
player.state.velocity[
|
|
58
|
-
|
|
59
|
-
other.state.
|
|
73
|
+
const nx = collision.normal[0], nz = collision.normal[2]
|
|
74
|
+
const minDist = physicsIntegration.config.capsuleRadius * 2
|
|
75
|
+
const overlap = minDist - collision.distance
|
|
76
|
+
const halfPush = overlap * 0.5
|
|
77
|
+
const pushVel = Math.min(halfPush / dt, 3.0)
|
|
78
|
+
player.state.position[0] -= nx * halfPush
|
|
79
|
+
player.state.position[2] -= nz * halfPush
|
|
80
|
+
player.state.velocity[0] -= nx * pushVel
|
|
81
|
+
player.state.velocity[2] -= nz * pushVel
|
|
82
|
+
other.state.position[0] += nx * halfPush
|
|
83
|
+
other.state.position[2] += nz * halfPush
|
|
84
|
+
other.state.velocity[0] += nx * pushVel
|
|
85
|
+
other.state.velocity[2] += nz * pushVel
|
|
86
|
+
physicsIntegration.setPlayerPosition(player.id, player.state.position)
|
|
87
|
+
physicsIntegration.setPlayerPosition(other.id, other.state.position)
|
|
60
88
|
}
|
|
61
89
|
}
|
|
90
|
+
const t2 = performance.now()
|
|
62
91
|
physics.step(dt)
|
|
92
|
+
const t3 = performance.now()
|
|
63
93
|
appRuntime.tick(tick, dt)
|
|
64
|
-
const
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
94
|
+
const t4 = performance.now()
|
|
95
|
+
if (players.length > 0) {
|
|
96
|
+
const playerSnap = networkState.getSnapshot()
|
|
97
|
+
snapshotSeq++
|
|
98
|
+
const isKeyframe = snapshotSeq % KEYFRAME_INTERVAL === 0
|
|
99
|
+
if (stageLoader && stageLoader.getActiveStage()) {
|
|
100
|
+
for (const player of players) {
|
|
101
|
+
const pos = player.state.position
|
|
102
|
+
const entitySnap = appRuntime.getSnapshotForPlayer(pos, stageLoader.getActiveStage().spatial.relevanceRadius)
|
|
103
|
+
const combined = { tick: playerSnap.tick, timestamp: playerSnap.timestamp, players: playerSnap.players, entities: entitySnap.entities }
|
|
104
|
+
if (isKeyframe || !playerEntityMaps.has(player.id)) {
|
|
105
|
+
const encoded = SnapshotEncoder.encode(combined)
|
|
106
|
+
const { entityMap } = SnapshotEncoder.encodeDelta(combined, new Map())
|
|
107
|
+
playerEntityMaps.set(player.id, entityMap)
|
|
108
|
+
connections.send(player.id, MSG.SNAPSHOT, { seq: snapshotSeq, ...encoded })
|
|
109
|
+
} else {
|
|
110
|
+
const prevMap = playerEntityMaps.get(player.id)
|
|
111
|
+
const { encoded, entityMap } = SnapshotEncoder.encodeDelta(combined, prevMap)
|
|
112
|
+
playerEntityMaps.set(player.id, entityMap)
|
|
113
|
+
connections.send(player.id, MSG.SNAPSHOT, { seq: snapshotSeq, ...encoded })
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
} else {
|
|
117
|
+
const entitySnap = appRuntime.getSnapshot()
|
|
70
118
|
const combined = { tick: playerSnap.tick, timestamp: playerSnap.timestamp, players: playerSnap.players, entities: entitySnap.entities }
|
|
71
|
-
|
|
119
|
+
if (isKeyframe || broadcastEntityMap.size === 0) {
|
|
120
|
+
const encoded = SnapshotEncoder.encode(combined)
|
|
121
|
+
const { entityMap } = SnapshotEncoder.encodeDelta(combined, new Map())
|
|
122
|
+
broadcastEntityMap = entityMap
|
|
123
|
+
connections.broadcast(MSG.SNAPSHOT, { seq: snapshotSeq, ...encoded })
|
|
124
|
+
} else {
|
|
125
|
+
const { encoded, entityMap } = SnapshotEncoder.encodeDelta(combined, broadcastEntityMap)
|
|
126
|
+
broadcastEntityMap = entityMap
|
|
127
|
+
connections.broadcast(MSG.SNAPSHOT, { seq: snapshotSeq, ...encoded })
|
|
128
|
+
}
|
|
72
129
|
}
|
|
73
|
-
} else {
|
|
74
|
-
const entitySnap = appRuntime.getSnapshot()
|
|
75
|
-
const combined = { tick: playerSnap.tick, timestamp: playerSnap.timestamp, players: playerSnap.players, entities: entitySnap.entities }
|
|
76
|
-
connections.broadcast(MSG.SNAPSHOT, { seq: snapshotSeq, ...SnapshotEncoder.encode(combined) })
|
|
77
130
|
}
|
|
131
|
+
for (const id of playerEntityMaps.keys()) {
|
|
132
|
+
if (!playerManager.getPlayer(id)) playerEntityMaps.delete(id)
|
|
133
|
+
}
|
|
134
|
+
const t5 = performance.now()
|
|
78
135
|
try {
|
|
79
136
|
appRuntime._drainReloadQueue()
|
|
80
137
|
} catch (e) {
|
|
81
138
|
console.error('[TickHandler] reload queue error:', e.message)
|
|
82
139
|
}
|
|
140
|
+
profileLog++
|
|
141
|
+
if (profileLog % 1280 === 0) {
|
|
142
|
+
const total = t5 - t0
|
|
143
|
+
const mem = process.memoryUsage()
|
|
144
|
+
const heap = (mem.heapUsed / 1048576).toFixed(1)
|
|
145
|
+
const rss = (mem.rss / 1048576).toFixed(1)
|
|
146
|
+
const ext = (mem.external / 1048576).toFixed(1)
|
|
147
|
+
const ab = (mem.arrayBuffers / 1048576).toFixed(1)
|
|
148
|
+
try { console.log(`[tick-profile] tick:${tick} players:${players.length} total:${total.toFixed(2)}ms | mv:${(t1-t0).toFixed(2)} col:${(t2-t1).toFixed(2)} phys:${(t3-t2).toFixed(2)} app:${(t4-t3).toFixed(2)} snap:${(t5-t4).toFixed(2)} | heap:${heap}MB rss:${rss}MB ext:${ext}MB ab:${ab}MB`) } catch (_) {}
|
|
149
|
+
}
|
|
83
150
|
}
|
|
84
151
|
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { join, dirname, resolve } from 'node:path'
|
|
2
|
+
import { fileURLToPath } from 'node:url'
|
|
3
|
+
import { existsSync, mkdirSync, readdirSync, copyFileSync, statSync } from 'node:fs'
|
|
4
|
+
|
|
5
|
+
const SDK_ROOT = join(dirname(fileURLToPath(import.meta.url)), '../..')
|
|
6
|
+
|
|
7
|
+
function copyDir(src, dest) {
|
|
8
|
+
mkdirSync(dest, { recursive: true })
|
|
9
|
+
for (const entry of readdirSync(src, { withFileTypes: true })) {
|
|
10
|
+
const s = join(src, entry.name)
|
|
11
|
+
const d = join(dest, entry.name)
|
|
12
|
+
if (entry.isDirectory()) copyDir(s, d)
|
|
13
|
+
else copyFileSync(s, d)
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export async function scaffold() {
|
|
18
|
+
const cwd = process.cwd()
|
|
19
|
+
const localApps = resolve(cwd, 'apps')
|
|
20
|
+
if (existsSync(localApps)) {
|
|
21
|
+
console.log(`[scaffold] apps/ already exists at ${localApps}, skipping`)
|
|
22
|
+
return
|
|
23
|
+
}
|
|
24
|
+
const sdkApps = join(SDK_ROOT, 'apps')
|
|
25
|
+
copyDir(sdkApps, localApps)
|
|
26
|
+
console.log(`[scaffold] created apps/ at ${localApps}`)
|
|
27
|
+
console.log(`[scaffold] run 'spoint' to start the server`)
|
|
28
|
+
}
|