@dcl-regenesislabs/bevy-headless-server 0.1.0-31413755159.commit-5b7586c

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 ADDED
@@ -0,0 +1,60 @@
1
+ # @dcl-regenesislabs/bevy-headless-server
2
+
3
+ Decentraland authoritative scene server, powered by the [bevy explorer](https://github.com/decentraland/bevy-explorer)
4
+ engine. A drop-in replacement for `@dcl/hammurabi-server`: same CLI contract, native binary
5
+ instead of Node + Babylon.
6
+
7
+ ```bash
8
+ npx @dcl-regenesislabs/bevy-headless-server --realm http://localhost:8000
9
+ ```
10
+
11
+ The Decentraland SDK spawns this automatically for scenes with `authoritativeMultiplayer`
12
+ enabled; you rarely need to run it by hand.
13
+
14
+ ## Options
15
+
16
+ | Flag | Meaning |
17
+ | --- | --- |
18
+ | `--realm <url>` | Realm to serve. Required. |
19
+ | `--position <x,y>` | Parcel to load. Defaults to `0,0`. |
20
+ | `--production` | Production mode; disables preview-only behaviour. |
21
+ | `--tick-hz <n>` | Scene tick rate. Defaults to 30. |
22
+ | `--timeout <secs>` | Exit cleanly after N seconds. |
23
+
24
+ `--scene-id`, `--private-key` and `--env` are accepted for hammurabi compatibility and ignored.
25
+
26
+ ## Orchestrated mode (multiplayer-server)
27
+
28
+ `--orchestrated` starts the engine in multi-scene worker mode: no realm needed, scenes are
29
+ added and removed over stdin (JSON lines) with pre-minted comms adapters, and control
30
+ events come back on stdout with the `@bevy-ctl ` prefix.
31
+
32
+ Orchestrators that manage the process themselves can skip the CLI and resolve the engine
33
+ path programmatically:
34
+
35
+ ```js
36
+ const { resolveBinary } = require('@dcl-regenesislabs/bevy-headless-server')
37
+ spawn(resolveBinary(), ['--orchestrated'], { stdio: ['pipe', 'pipe', 'inherit'] })
38
+ ```
39
+
40
+ ## How the binary is delivered
41
+
42
+ The engine ships as four platform packages (`darwin-arm64`, `darwin-x64`, `linux-x64`,
43
+ `win32-x64`) listed under `optionalDependencies`; your package manager installs only the one
44
+ matching your machine. Each contains two files that **must stay in the same directory** —
45
+ the engine execs its scene-runtime sidecar from its own location.
46
+
47
+ Set `DCL_BEVY_SERVER_PATH` to an absolute path to use a pre-installed engine instead
48
+ (useful for Electron hosts that bundle their own copy).
49
+
50
+ Exit code `78` means the engine is permanently unavailable here — unsupported platform,
51
+ missing binary, or bad arguments — so a caller can fall back to another implementation
52
+ instead of retrying.
53
+
54
+ ## Linux runtime dependencies
55
+
56
+ The engine links the graphics/audio stack even when running headless:
57
+
58
+ ```bash
59
+ sudo apt install libasound2 libudev1 libgl1 libx11-6 libxext6
60
+ ```
package/bin/cli.js ADDED
@@ -0,0 +1,156 @@
1
+ #!/usr/bin/env node
2
+ 'use strict'
3
+
4
+ const { spawn } = require('child_process')
5
+ const path = require('path')
6
+ const {
7
+ resolveBinary,
8
+ UnsupportedPlatformError,
9
+ MissingBinaryError
10
+ } = require('../lib/resolve-binary')
11
+
12
+ // Exit code 78 (EX_CONFIG) means "permanently unavailable here" — callers use it to
13
+ // fall back to another server implementation instead of retrying.
14
+ const EXIT_UNAVAILABLE = 78
15
+
16
+ const USAGE = `
17
+ Decentraland authoritative scene server (bevy engine)
18
+
19
+ Usage: bevy-headless-server --realm <url> [options]
20
+
21
+ --realm <url> Realm to serve. Required unless --orchestrated.
22
+ --position <x,y> Parcel to load. Default 0,0.
23
+ --production Production mode (disables preview-only behaviour).
24
+ --orchestrated Multi-scene mode driven over stdin by an orchestrator
25
+ (the multiplayer-server worker contract).
26
+ --tick-hz <n> Scene tick rate. Default 30.
27
+ --timeout <secs> Exit cleanly after N seconds.
28
+ --version Print version and exit.
29
+ -h, --help This message.
30
+
31
+ Environment:
32
+ DCL_BEVY_SERVER_PATH Absolute path to a pre-installed \`headless\` binary,
33
+ bypassing the bundled platform package.
34
+ `
35
+
36
+ function fail(message, code) {
37
+ process.stderr.write(`bevy-headless-server: ${message}\n`)
38
+ process.exit(code)
39
+ }
40
+
41
+ /** Translate the hammurabi-server CLI contract into bevy-headless flags. */
42
+ function translate(argv) {
43
+ const out = []
44
+ let realm = null
45
+ let position = null
46
+ let production = false
47
+ let orchestrated = false
48
+ const passthrough = { '--tick-hz': true, '--timeout': true, '--scene-threads': true }
49
+
50
+ for (let i = 0; i < argv.length; i++) {
51
+ const arg = argv[i]
52
+ // hammurabi accepts --flag=value; bevy's parser wants them separated.
53
+ const eq = arg.indexOf('=')
54
+ const name = eq === -1 ? arg : arg.slice(0, eq)
55
+ const inlineValue = eq === -1 ? null : arg.slice(eq + 1)
56
+ const takeValue = () => (inlineValue !== null ? inlineValue : argv[++i])
57
+
58
+ switch (name) {
59
+ case '--realm':
60
+ realm = takeValue()
61
+ break
62
+ case '--position':
63
+ position = takeValue()
64
+ break
65
+ case '--production':
66
+ production = true
67
+ break
68
+ case '--orchestrated':
69
+ orchestrated = true
70
+ break
71
+ case '-h':
72
+ case '--help':
73
+ process.stdout.write(USAGE)
74
+ process.exit(0)
75
+ break
76
+ case '--version':
77
+ process.stdout.write(`${require('../package.json').version}\n`)
78
+ process.exit(0)
79
+ break
80
+ // Accepted by hammurabi, no equivalent here. Warn rather than fail so an
81
+ // orchestrator passing extra flags still boots.
82
+ case '--scene-id':
83
+ case '--private-key':
84
+ case '--env':
85
+ takeValue()
86
+ process.stderr.write(`bevy-headless-server: ignoring unsupported flag ${name}\n`)
87
+ break
88
+ default:
89
+ if (passthrough[name]) {
90
+ out.push(name, takeValue())
91
+ } else {
92
+ process.stderr.write(`bevy-headless-server: ignoring unknown flag ${arg}\n`)
93
+ }
94
+ }
95
+ }
96
+
97
+ // orchestrated mode gets scenes (with their content URLs) over stdin, so no realm
98
+ if (!realm && !orchestrated) fail('--realm is required', EXIT_UNAVAILABLE)
99
+ if (realm) {
100
+ try {
101
+ // eslint-disable-next-line no-new
102
+ new URL(realm)
103
+ } catch (e) {
104
+ fail(`--realm is not a valid URL: ${realm}`, EXIT_UNAVAILABLE)
105
+ }
106
+ }
107
+ if (position && !/^-?\d+,-?\d+$/.test(position)) {
108
+ fail(`--position must be "x,y", got: ${position}`, EXIT_UNAVAILABLE)
109
+ }
110
+
111
+ const args = orchestrated ? ['--orchestrated'] : ['--server-mode']
112
+ if (realm) args.unshift('--realm', realm)
113
+ if (position) args.push('--location', position)
114
+ if (!production) args.push('--preview')
115
+ return args.concat(out)
116
+ }
117
+
118
+ function main() {
119
+ const args = translate(process.argv.slice(2))
120
+
121
+ let exe
122
+ try {
123
+ exe = resolveBinary()
124
+ } catch (err) {
125
+ if (err instanceof UnsupportedPlatformError || err instanceof MissingBinaryError) {
126
+ fail(err.message, EXIT_UNAVAILABLE)
127
+ }
128
+ throw err
129
+ }
130
+
131
+ const child = spawn(exe, args, { stdio: 'inherit', env: process.env })
132
+
133
+ child.on('error', (err) => {
134
+ if (process.platform === 'linux' && /ENOENT|not found/i.test(err.message)) {
135
+ process.stderr.write(
136
+ 'bevy-headless-server: the engine failed to start. On Linux it needs:\n' +
137
+ ' sudo apt install libasound2 libudev1 libgl1 libx11-6 libxext6\n'
138
+ )
139
+ process.exit(EXIT_UNAVAILABLE)
140
+ }
141
+ fail(`failed to start the engine: ${err.message}`, EXIT_UNAVAILABLE)
142
+ })
143
+
144
+ const forward = (signal) => () => {
145
+ if (!child.killed) child.kill(signal)
146
+ }
147
+ process.on('SIGTERM', forward('SIGTERM'))
148
+ process.on('SIGINT', forward('SIGINT'))
149
+ process.on('exit', forward('SIGTERM'))
150
+
151
+ child.on('close', (code, signal) => {
152
+ process.exit(signal ? 1 : code === null ? 1 : code)
153
+ })
154
+ }
155
+
156
+ main()
package/install.js ADDED
@@ -0,0 +1,20 @@
1
+ 'use strict'
2
+
3
+ // Advisory postinstall check. The binary itself arrives through optionalDependencies;
4
+ // this only turns npm's silent optional-dependency skip (npm/cli#4828, and every
5
+ // --ignore-scripts / --no-optional install) into a readable message at install time
6
+ // rather than a confusing failure at first run.
7
+
8
+ const { resolveBinary, platformKey } = require('./lib/resolve-binary')
9
+
10
+ try {
11
+ resolveBinary()
12
+ } catch (err) {
13
+ const key = platformKey()
14
+ process.stderr.write(
15
+ `\n@dcl-regenesislabs/bevy-headless-server: ${err.message}\n` +
16
+ (key
17
+ ? ` Fix with: npm install @dcl-regenesislabs/bevy-headless-server-${key}@${require('./package.json').version}\n\n`
18
+ : '\n')
19
+ )
20
+ }
@@ -0,0 +1,82 @@
1
+ 'use strict'
2
+
3
+ const fs = require('fs')
4
+ const path = require('path')
5
+
6
+ // Platform packages ship `headless` and `dcl_deno_ipc` side by side. The engine
7
+ // spawns the sidecar from its own directory under a fixed name, so the pair must
8
+ // never be split or copied apart.
9
+ const SUPPORTED = {
10
+ 'darwin arm64': 'darwin-arm64',
11
+ 'darwin x64': 'darwin-x64',
12
+ 'linux x64': 'linux-x64',
13
+ 'win32 x64': 'win32-x64'
14
+ }
15
+
16
+ function platformKey() {
17
+ return SUPPORTED[`${process.platform} ${process.arch}`]
18
+ }
19
+
20
+ class UnsupportedPlatformError extends Error {}
21
+ class MissingBinaryError extends Error {}
22
+
23
+ function resolveFromPackage(key) {
24
+ const pkg = `@dcl-regenesislabs/bevy-headless-server-${key}`
25
+ const dir = path.dirname(require.resolve(`${pkg}/package.json`))
26
+ return path.join(dir, 'bin')
27
+ }
28
+
29
+ /**
30
+ * Absolute path to the `headless` executable.
31
+ * DCL_BEVY_SERVER_PATH overrides everything (Creator Hub / pre-seeded installs).
32
+ */
33
+ function resolveBinary() {
34
+ const override = process.env.DCL_BEVY_SERVER_PATH
35
+ if (override) {
36
+ if (!fs.existsSync(override)) {
37
+ throw new MissingBinaryError(`DCL_BEVY_SERVER_PATH points at a missing file: ${override}`)
38
+ }
39
+ return verifySidecar(override)
40
+ }
41
+
42
+ const key = platformKey()
43
+ if (!key) {
44
+ throw new UnsupportedPlatformError(
45
+ `no bevy-headless build for ${process.platform}-${process.arch} ` +
46
+ `(supported: ${Object.values(SUPPORTED).join(', ')})`
47
+ )
48
+ }
49
+
50
+ let binDir
51
+ try {
52
+ binDir = resolveFromPackage(key)
53
+ } catch (err) {
54
+ throw new MissingBinaryError(
55
+ `@dcl-regenesislabs/bevy-headless-server-${key} is not installed. ` +
56
+ `If your package manager skipped optional dependencies, reinstall with them enabled ` +
57
+ `or install that package explicitly.`
58
+ )
59
+ }
60
+
61
+ const exe = path.join(binDir, process.platform === 'win32' ? 'headless.exe' : 'headless')
62
+ if (!fs.existsSync(exe)) {
63
+ throw new MissingBinaryError(`corrupt install: ${exe} is missing — reinstall the package`)
64
+ }
65
+ return verifySidecar(exe)
66
+ }
67
+
68
+ function verifySidecar(exe) {
69
+ const sidecar = path.join(
70
+ path.dirname(exe),
71
+ process.platform === 'win32' ? 'dcl_deno_ipc.exe' : 'dcl_deno_ipc'
72
+ )
73
+ if (!fs.existsSync(sidecar)) {
74
+ throw new MissingBinaryError(
75
+ `the scene runtime sidecar is missing next to the engine (expected ${sidecar}). ` +
76
+ `The two binaries must live in the same directory.`
77
+ )
78
+ }
79
+ return exe
80
+ }
81
+
82
+ module.exports = { resolveBinary, platformKey, SUPPORTED, UnsupportedPlatformError, MissingBinaryError }
package/package.json ADDED
@@ -0,0 +1,34 @@
1
+ {
2
+ "name": "@dcl-regenesislabs/bevy-headless-server",
3
+ "version": "0.1.0-31413755159.commit-5b7586c",
4
+ "description": "Decentraland authoritative scene server (bevy engine) — drop-in replacement for @dcl/hammurabi-server",
5
+ "license": "Apache-2.0",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "https://github.com/decentraland/bevy-explorer.git",
9
+ "directory": "deploy/headless/launcher"
10
+ },
11
+ "bin": {
12
+ "bevy-headless-server": "bin/cli.js"
13
+ },
14
+ "main": "lib/resolve-binary.js",
15
+ "files": [
16
+ "bin/cli.js",
17
+ "lib/resolve-binary.js",
18
+ "install.js",
19
+ "README.md"
20
+ ],
21
+ "scripts": {
22
+ "postinstall": "node install.js"
23
+ },
24
+ "engines": {
25
+ "node": ">=18"
26
+ },
27
+ "optionalDependencies": {
28
+ "@dcl-regenesislabs/bevy-headless-server-darwin-arm64": "0.1.0-31413755159.commit-5b7586c",
29
+ "@dcl-regenesislabs/bevy-headless-server-darwin-x64": "0.1.0-31413755159.commit-5b7586c",
30
+ "@dcl-regenesislabs/bevy-headless-server-linux-x64": "0.1.0-31413755159.commit-5b7586c",
31
+ "@dcl-regenesislabs/bevy-headless-server-win32-x64": "0.1.0-31413755159.commit-5b7586c"
32
+ },
33
+ "commit": "5b7586c8b7e79106680c4d7fb8ab8fad842531b8"
34
+ }