@tsukilabs/nil-ffi 0.7.2 → 0.7.4
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/def.d.ts +24 -0
- package/dist/index.d.ts +33 -5
- package/dist/index.js +376 -192
- package/dist/queue.d.ts +2 -2
- package/package.json +2 -2
package/dist/def.d.ts
CHANGED
|
@@ -16,6 +16,22 @@ export declare const definitions: {
|
|
|
16
16
|
readonly arguments: readonly [ffi.types.UINT_32];
|
|
17
17
|
readonly return: ffi.types.VOID;
|
|
18
18
|
};
|
|
19
|
+
readonly nil_is_host: {
|
|
20
|
+
readonly arguments: readonly [ffi.types.UINT_32];
|
|
21
|
+
readonly return: ffi.types.VOID;
|
|
22
|
+
};
|
|
23
|
+
readonly nil_start_server: {
|
|
24
|
+
readonly arguments: readonly [ffi.types.UINT_32, ffi.types.POINTER];
|
|
25
|
+
readonly return: ffi.types.VOID;
|
|
26
|
+
};
|
|
27
|
+
readonly nil_start_server_with_savedata: {
|
|
28
|
+
readonly arguments: readonly [ffi.types.UINT_32, ffi.types.POINTER];
|
|
29
|
+
readonly return: ffi.types.VOID;
|
|
30
|
+
};
|
|
31
|
+
readonly nil_stop_server: {
|
|
32
|
+
readonly arguments: readonly [ffi.types.UINT_32];
|
|
33
|
+
readonly return: ffi.types.VOID;
|
|
34
|
+
};
|
|
19
35
|
readonly nil_client_version: {
|
|
20
36
|
readonly arguments: readonly [ffi.types.UINT_32];
|
|
21
37
|
readonly return: ffi.types.VOID;
|
|
@@ -56,6 +72,14 @@ export declare const definitions: {
|
|
|
56
72
|
readonly arguments: readonly [ffi.types.UINT_32];
|
|
57
73
|
readonly return: ffi.types.VOID;
|
|
58
74
|
};
|
|
75
|
+
readonly nil_runtime_num_alive_tasks: {
|
|
76
|
+
readonly arguments: readonly [ffi.types.UINT_32];
|
|
77
|
+
readonly return: ffi.types.VOID;
|
|
78
|
+
};
|
|
79
|
+
readonly nil_runtime_num_workers: {
|
|
80
|
+
readonly arguments: readonly [ffi.types.UINT_32];
|
|
81
|
+
readonly return: ffi.types.VOID;
|
|
82
|
+
};
|
|
59
83
|
readonly nil_add_academy_recruit_order: {
|
|
60
84
|
readonly arguments: readonly [ffi.types.UINT_32, ffi.types.POINTER];
|
|
61
85
|
readonly return: ffi.types.VOID;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { definitions } from './def';
|
|
2
2
|
import { Option } from '@tb-dev/utils';
|
|
3
|
-
import {
|
|
3
|
+
import { QueueOptions } from './queue';
|
|
4
|
+
import { AuthorizeRequest, ffi_UpdateClient, GetPlayerIdsRequest, GetPlayerIdsResponse, GetPlayerRequest, GetPlayerStatusRequest, GetRemoteWorldsResponse, LocalServer, PlayerExistsRequest, ServerAddr, SetPlayerStatusRequest, SpawnPlayerRequest, ValidateTokenRequest, ValidateTokenResponse, WorldOptions } from '@tsukilabs/nil-bindings';
|
|
4
5
|
import * as ffi from "node:ffi";
|
|
5
6
|
export * from './def';
|
|
6
7
|
export * from './error';
|
|
@@ -8,21 +9,48 @@ export type Handle = ffi.DynamicLibraryResult<typeof definitions>;
|
|
|
8
9
|
export declare const VERSION: string;
|
|
9
10
|
export declare const USER_AGENT: string;
|
|
10
11
|
export declare class Nil implements Disposable {
|
|
12
|
+
readonly path: string;
|
|
11
13
|
private readonly handle;
|
|
12
14
|
readonly functions: Handle["functions"];
|
|
13
15
|
private readonly queue;
|
|
14
16
|
private disposed;
|
|
15
17
|
private constructor();
|
|
16
|
-
static init(dll: string): Promise<Nil>;
|
|
18
|
+
static init(dll: string, options?: NilOptions): Promise<Nil>;
|
|
19
|
+
static initLatest(outDir?: Option<string>, options?: NilOptions): Promise<Nil>;
|
|
20
|
+
static downloadLatest(outDir?: Option<string>): Promise<string>;
|
|
17
21
|
[Symbol.dispose](): void;
|
|
18
22
|
close(): void;
|
|
23
|
+
isClosed(): boolean;
|
|
24
|
+
authorize(req: AuthorizeRequest): Promise<string>;
|
|
19
25
|
getClientVersion(): Promise<string>;
|
|
20
26
|
getFfiVersion(): Promise<string>;
|
|
27
|
+
getPlayer(req: GetPlayerRequest): Promise<import('@tsukilabs/nil-bindings').Player>;
|
|
28
|
+
getPlayerIds(req: GetPlayerIdsRequest): Promise<GetPlayerIdsResponse>;
|
|
29
|
+
getPlayerStatus(req: GetPlayerStatusRequest): Promise<import('@tsukilabs/nil-bindings').PlayerStatus>;
|
|
21
30
|
getRemoteWorldLimit(): Promise<number>;
|
|
22
31
|
getRemoteWorldLimitPerUser(): Promise<number>;
|
|
23
32
|
getRemoteWorlds(): Promise<GetRemoteWorldsResponse>;
|
|
33
|
+
getRuntimeNumAliveTasks(): Promise<number>;
|
|
34
|
+
getRuntimeNumWorkers(): Promise<number>;
|
|
35
|
+
getServerAddr(): Promise<ServerAddr>;
|
|
36
|
+
getServerKind(): Promise<import('@tsukilabs/nil-bindings').ServerKind>;
|
|
24
37
|
getUserAgent(): Promise<string>;
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
38
|
+
getWorldId(): Promise<Option<string>>;
|
|
39
|
+
isHost(): Promise<boolean>;
|
|
40
|
+
isLocal(): Promise<boolean>;
|
|
41
|
+
isReady(): Promise<boolean>;
|
|
42
|
+
isRemote(): Promise<boolean>;
|
|
43
|
+
playerExists(req: PlayerExistsRequest): Promise<boolean>;
|
|
44
|
+
setPlayerStatus(req: SetPlayerStatusRequest): Promise<import('@tb-dev/utils').nil>;
|
|
45
|
+
setUserAgent(userAgent: string): Promise<import('@tb-dev/utils').nil>;
|
|
46
|
+
spawnPlayer(req: SpawnPlayerRequest): Promise<import('@tb-dev/utils').nil>;
|
|
47
|
+
startServer(options: WorldOptions): Promise<LocalServer>;
|
|
48
|
+
startServerWithSavedata(path: string): Promise<LocalServer>;
|
|
49
|
+
stopClient(): Promise<import('@tb-dev/utils').nil>;
|
|
50
|
+
stopServer(): Promise<import('@tb-dev/utils').nil>;
|
|
51
|
+
updateClient(options: ffi_UpdateClient): Promise<import('@tb-dev/utils').nil>;
|
|
52
|
+
validateToken(req: ValidateTokenRequest): Promise<ValidateTokenResponse>;
|
|
53
|
+
}
|
|
54
|
+
export interface NilOptions {
|
|
55
|
+
onError?: QueueOptions["onError"];
|
|
28
56
|
}
|
package/dist/index.js
CHANGED
|
@@ -1,179 +1,11 @@
|
|
|
1
1
|
import * as ffi from "node:ffi";
|
|
2
|
-
import { EventEmitter } from "node:events";
|
|
3
|
-
import { isNil } from "es-toolkit/predicate";
|
|
4
2
|
import * as process from "node:process";
|
|
3
|
+
import { cwd } from "node:process";
|
|
5
4
|
import { ffi_Status } from "@tsukilabs/nil-bindings";
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
#current = 0;
|
|
11
|
-
next() {
|
|
12
|
-
if (this.#current >= 4294967295) this.#current = 0;
|
|
13
|
-
return ++this.#current;
|
|
14
|
-
}
|
|
15
|
-
};
|
|
16
|
-
//#endregion
|
|
17
|
-
//#region src/ptr.ts
|
|
18
|
-
//! Copyright (C) Call of Nil contributors
|
|
19
|
-
//! SPDX-License-Identifier: AGPL-3.0-only
|
|
20
|
-
function allocBuffer(size) {
|
|
21
|
-
return Buffer.alloc(size ?? pointerSize());
|
|
22
|
-
}
|
|
23
|
-
function readBufferPtr(buffer) {
|
|
24
|
-
if (pointerSize() === 8) return buffer.readBigUInt64LE();
|
|
25
|
-
else return BigInt(buffer.readUInt32LE());
|
|
26
|
-
}
|
|
27
|
-
function pointerSize() {
|
|
28
|
-
if (process.arch === "x64" || process.arch === "arm64") return 8;
|
|
29
|
-
else return 4;
|
|
30
|
-
}
|
|
31
|
-
//#endregion
|
|
32
|
-
//#region src/error.ts
|
|
33
|
-
//! Copyright (C) Call of Nil contributors
|
|
34
|
-
//! SPDX-License-Identifier: AGPL-3.0-only
|
|
35
|
-
var NilError = class NilError extends Error {
|
|
36
|
-
constructor(message, options) {
|
|
37
|
-
const status = options?.status ?? ffi_Status.ERR_UNKNOWN;
|
|
38
|
-
if (!message && status !== ffi_Status.OK) {
|
|
39
|
-
for (const [key, value] of Object.entries(ffi_Status)) if (value === status) {
|
|
40
|
-
message = key;
|
|
41
|
-
break;
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
message ??= "ERR_UNKNOWN";
|
|
45
|
-
super(message, options);
|
|
46
|
-
this.name = "NilError";
|
|
47
|
-
this.status = status;
|
|
48
|
-
}
|
|
49
|
-
static fromStatus(status) {
|
|
50
|
-
return new NilError(null, { status });
|
|
51
|
-
}
|
|
52
|
-
};
|
|
53
|
-
var HandleClosedError = class extends Error {
|
|
54
|
-
constructor(options) {
|
|
55
|
-
super("handle is closed", options);
|
|
56
|
-
this.name = "HandleClosedError";
|
|
57
|
-
}
|
|
58
|
-
};
|
|
59
|
-
var UnknownResponseError = class extends Error {
|
|
60
|
-
constructor(response, options) {
|
|
61
|
-
super(`got unknown response with id ${response.id}`, options);
|
|
62
|
-
this.name = "UnknownResponseError";
|
|
63
|
-
this.response = response;
|
|
64
|
-
}
|
|
65
|
-
};
|
|
66
|
-
//#endregion
|
|
67
|
-
//#region src/queue.ts
|
|
68
|
-
//! Copyright (C) Call of Nil contributors
|
|
69
|
-
//! SPDX-License-Identifier: AGPL-3.0-only
|
|
70
|
-
var QueueEmitter = class extends EventEmitter {
|
|
71
|
-
emit(event, ...args) {
|
|
72
|
-
return super.emit(event, ...args);
|
|
73
|
-
}
|
|
74
|
-
on(event, listener) {
|
|
75
|
-
return super.on(event, listener);
|
|
76
|
-
}
|
|
77
|
-
};
|
|
78
|
-
var Queue = class Queue {
|
|
79
|
-
static {
|
|
80
|
-
this.POLL_INTERVAL = 16;
|
|
81
|
-
}
|
|
82
|
-
static {
|
|
83
|
-
this.LIMIT_PER_TICK = 200;
|
|
84
|
-
}
|
|
85
|
-
constructor(handle, options) {
|
|
86
|
-
this.disposed = false;
|
|
87
|
-
this.emitter = new QueueEmitter();
|
|
88
|
-
this.requestId = new RequestId();
|
|
89
|
-
this.pendingRequests = /* @__PURE__ */ new Map();
|
|
90
|
-
this.functions = handle.functions;
|
|
91
|
-
this.errorHandler = options?.onError ?? console.error;
|
|
92
|
-
this.emitter.on("error", this.errorHandler);
|
|
93
|
-
this.emitter.on("response", this.onResponse.bind(this));
|
|
94
|
-
this.timer = setTimeout(() => this.drain(), 0);
|
|
95
|
-
}
|
|
96
|
-
[Symbol.dispose]() {
|
|
97
|
-
if (this.disposed) return;
|
|
98
|
-
this.disposed = true;
|
|
99
|
-
this.rejectAll(new HandleClosedError());
|
|
100
|
-
this.emitter.removeAllListeners();
|
|
101
|
-
}
|
|
102
|
-
request(start) {
|
|
103
|
-
if (this.disposed) return Promise.reject(new HandleClosedError());
|
|
104
|
-
const requestId = this.requestId.next();
|
|
105
|
-
return new Promise((resolve, reject) => {
|
|
106
|
-
this.pendingRequests.set(requestId, {
|
|
107
|
-
resolve,
|
|
108
|
-
reject
|
|
109
|
-
});
|
|
110
|
-
try {
|
|
111
|
-
start(requestId);
|
|
112
|
-
} catch (err) {
|
|
113
|
-
this.pendingRequests.delete(requestId);
|
|
114
|
-
reject(err);
|
|
115
|
-
}
|
|
116
|
-
});
|
|
117
|
-
}
|
|
118
|
-
drain() {
|
|
119
|
-
if (this.disposed) return;
|
|
120
|
-
this.clearTimeout();
|
|
121
|
-
try {
|
|
122
|
-
for (let i = 0; i < Queue.LIMIT_PER_TICK; i++) if (!this.poll()) break;
|
|
123
|
-
} catch (err) {
|
|
124
|
-
this.emitter.emit("error", err);
|
|
125
|
-
}
|
|
126
|
-
this.timer = setTimeout(() => this.drain(), Queue.POLL_INTERVAL);
|
|
127
|
-
}
|
|
128
|
-
poll() {
|
|
129
|
-
const out = allocBuffer();
|
|
130
|
-
const status = this.functions.nil_ffi_poll(out);
|
|
131
|
-
if (status === ffi_Status.ERR_NOTHING_TO_POLL) return false;
|
|
132
|
-
if (status !== ffi_Status.OK) throw NilError.fromStatus(status);
|
|
133
|
-
const ptr = readBufferPtr(out);
|
|
134
|
-
if (ptr === 0n) throw new Error("failed to poll: unexpected null pointer");
|
|
135
|
-
try {
|
|
136
|
-
const json = ffi.toString(ptr) ?? "";
|
|
137
|
-
const entry = JSON.parse(json);
|
|
138
|
-
this.dispatch(entry);
|
|
139
|
-
} finally {
|
|
140
|
-
this.functions.nil_ffi_free_str(ptr);
|
|
141
|
-
}
|
|
142
|
-
return true;
|
|
143
|
-
}
|
|
144
|
-
dispatch(entry) {
|
|
145
|
-
switch (entry.kind) {
|
|
146
|
-
case "response": {
|
|
147
|
-
const response = JSON.parse(entry.json_str);
|
|
148
|
-
this.emitter.emit("response", response);
|
|
149
|
-
break;
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
rejectAll(error) {
|
|
154
|
-
this.clearTimeout();
|
|
155
|
-
this.pendingRequests.values().forEach(({ reject }) => reject(error));
|
|
156
|
-
this.pendingRequests.clear();
|
|
157
|
-
}
|
|
158
|
-
clearTimeout() {
|
|
159
|
-
if (!isNil(this.timer)) {
|
|
160
|
-
clearTimeout(this.timer);
|
|
161
|
-
this.timer = null;
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
onResponse(response) {
|
|
165
|
-
const pending = this.pendingRequests.get(response.id);
|
|
166
|
-
if (pending) {
|
|
167
|
-
this.pendingRequests.delete(response.id);
|
|
168
|
-
if (response.kind === "ok") pending.resolve(response.data);
|
|
169
|
-
else {
|
|
170
|
-
const { status, error } = response;
|
|
171
|
-
pending.reject(new NilError(error, { status }));
|
|
172
|
-
}
|
|
173
|
-
} else this.emitter.emit("error", new UnknownResponseError(response));
|
|
174
|
-
}
|
|
175
|
-
};
|
|
176
|
-
//#endregion
|
|
5
|
+
import { writeFile } from "node:fs/promises";
|
|
6
|
+
import { EventEmitter } from "node:events";
|
|
7
|
+
import { isNil } from "es-toolkit/predicate";
|
|
8
|
+
import { join, resolve } from "node:path";
|
|
177
9
|
//#region src/def.ts
|
|
178
10
|
//! Copyright (C) Call of Nil contributors
|
|
179
11
|
//! SPDX-License-Identifier: AGPL-3.0-only
|
|
@@ -194,6 +26,22 @@ var definitions = {
|
|
|
194
26
|
arguments: [ffi.types.UINT_32],
|
|
195
27
|
return: ffi.types.VOID
|
|
196
28
|
},
|
|
29
|
+
nil_is_host: {
|
|
30
|
+
arguments: [ffi.types.UINT_32],
|
|
31
|
+
return: ffi.types.VOID
|
|
32
|
+
},
|
|
33
|
+
nil_start_server: {
|
|
34
|
+
arguments: [ffi.types.UINT_32, ffi.types.POINTER],
|
|
35
|
+
return: ffi.types.VOID
|
|
36
|
+
},
|
|
37
|
+
nil_start_server_with_savedata: {
|
|
38
|
+
arguments: [ffi.types.UINT_32, ffi.types.POINTER],
|
|
39
|
+
return: ffi.types.VOID
|
|
40
|
+
},
|
|
41
|
+
nil_stop_server: {
|
|
42
|
+
arguments: [ffi.types.UINT_32],
|
|
43
|
+
return: ffi.types.VOID
|
|
44
|
+
},
|
|
197
45
|
nil_client_version: {
|
|
198
46
|
arguments: [ffi.types.UINT_32],
|
|
199
47
|
return: ffi.types.VOID
|
|
@@ -234,6 +82,14 @@ var definitions = {
|
|
|
234
82
|
arguments: [ffi.types.UINT_32],
|
|
235
83
|
return: ffi.types.VOID
|
|
236
84
|
},
|
|
85
|
+
nil_runtime_num_alive_tasks: {
|
|
86
|
+
arguments: [ffi.types.UINT_32],
|
|
87
|
+
return: ffi.types.VOID
|
|
88
|
+
},
|
|
89
|
+
nil_runtime_num_workers: {
|
|
90
|
+
arguments: [ffi.types.UINT_32],
|
|
91
|
+
return: ffi.types.VOID
|
|
92
|
+
},
|
|
237
93
|
nil_add_academy_recruit_order: {
|
|
238
94
|
arguments: [ffi.types.UINT_32, ffi.types.POINTER],
|
|
239
95
|
return: ffi.types.VOID
|
|
@@ -724,24 +580,220 @@ var definitions = {
|
|
|
724
580
|
}
|
|
725
581
|
};
|
|
726
582
|
//#endregion
|
|
583
|
+
//#region src/error.ts
|
|
584
|
+
//! Copyright (C) Call of Nil contributors
|
|
585
|
+
//! SPDX-License-Identifier: AGPL-3.0-only
|
|
586
|
+
var NilError = class NilError extends Error {
|
|
587
|
+
constructor(message, options) {
|
|
588
|
+
const status = options?.status ?? ffi_Status.ERR_UNKNOWN;
|
|
589
|
+
if (!message && status !== ffi_Status.OK) {
|
|
590
|
+
for (const [key, value] of Object.entries(ffi_Status)) if (value === status) {
|
|
591
|
+
message = key;
|
|
592
|
+
break;
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
message ??= "ERR_UNKNOWN";
|
|
596
|
+
super(message, options);
|
|
597
|
+
this.name = "NilError";
|
|
598
|
+
this.status = status;
|
|
599
|
+
}
|
|
600
|
+
static fromStatus(status) {
|
|
601
|
+
return new NilError(null, { status });
|
|
602
|
+
}
|
|
603
|
+
};
|
|
604
|
+
var HandleClosedError = class extends Error {
|
|
605
|
+
constructor(options) {
|
|
606
|
+
super("handle is closed", options);
|
|
607
|
+
this.name = "HandleClosedError";
|
|
608
|
+
}
|
|
609
|
+
};
|
|
610
|
+
var UnknownResponseError = class extends Error {
|
|
611
|
+
constructor(response, options) {
|
|
612
|
+
super(`got unknown response with id ${response.id}`, options);
|
|
613
|
+
this.name = "UnknownResponseError";
|
|
614
|
+
this.response = response;
|
|
615
|
+
}
|
|
616
|
+
};
|
|
617
|
+
//#endregion
|
|
618
|
+
//#region src/request.ts
|
|
619
|
+
//! Copyright (C) Call of Nil contributors
|
|
620
|
+
//! SPDX-License-Identifier: AGPL-3.0-only
|
|
621
|
+
var RequestId = class {
|
|
622
|
+
#current = 0;
|
|
623
|
+
next() {
|
|
624
|
+
if (this.#current >= 4294967295) this.#current = 0;
|
|
625
|
+
return ++this.#current;
|
|
626
|
+
}
|
|
627
|
+
};
|
|
628
|
+
//#endregion
|
|
629
|
+
//#region src/ptr.ts
|
|
630
|
+
//! Copyright (C) Call of Nil contributors
|
|
631
|
+
//! SPDX-License-Identifier: AGPL-3.0-only
|
|
632
|
+
function allocBuffer(size) {
|
|
633
|
+
return Buffer.alloc(size ?? pointerSize());
|
|
634
|
+
}
|
|
635
|
+
function readBufferPtr(buffer) {
|
|
636
|
+
if (pointerSize() === 8) return buffer.readBigUInt64LE();
|
|
637
|
+
else return BigInt(buffer.readUInt32LE());
|
|
638
|
+
}
|
|
639
|
+
function pointerSize() {
|
|
640
|
+
if (process.arch === "x64" || process.arch === "arm64") return 8;
|
|
641
|
+
else return 4;
|
|
642
|
+
}
|
|
643
|
+
//#endregion
|
|
644
|
+
//#region src/queue.ts
|
|
645
|
+
//! Copyright (C) Call of Nil contributors
|
|
646
|
+
//! SPDX-License-Identifier: AGPL-3.0-only
|
|
647
|
+
var QueueEmitter = class extends EventEmitter {
|
|
648
|
+
emit(event, ...args) {
|
|
649
|
+
return super.emit(event, ...args);
|
|
650
|
+
}
|
|
651
|
+
on(event, listener) {
|
|
652
|
+
return super.on(event, listener);
|
|
653
|
+
}
|
|
654
|
+
};
|
|
655
|
+
var Queue = class Queue {
|
|
656
|
+
static {
|
|
657
|
+
this.POLL_INTERVAL = 16;
|
|
658
|
+
}
|
|
659
|
+
static {
|
|
660
|
+
this.LIMIT_PER_TICK = 200;
|
|
661
|
+
}
|
|
662
|
+
constructor(handle, options) {
|
|
663
|
+
this.disposed = false;
|
|
664
|
+
this.emitter = new QueueEmitter();
|
|
665
|
+
this.requestId = new RequestId();
|
|
666
|
+
this.pendingRequests = /* @__PURE__ */ new Map();
|
|
667
|
+
this.functions = handle.functions;
|
|
668
|
+
this.errorHandler = options?.onError ?? console.error;
|
|
669
|
+
this.emitter.on("error", this.errorHandler);
|
|
670
|
+
this.emitter.on("response", this.onResponse.bind(this));
|
|
671
|
+
this.timer = setTimeout(() => this.drain(), 0);
|
|
672
|
+
}
|
|
673
|
+
[Symbol.dispose]() {
|
|
674
|
+
if (this.disposed) return;
|
|
675
|
+
this.disposed = true;
|
|
676
|
+
this.rejectAll(new HandleClosedError());
|
|
677
|
+
this.emitter.removeAllListeners();
|
|
678
|
+
}
|
|
679
|
+
request(start) {
|
|
680
|
+
if (this.disposed) return Promise.reject(new HandleClosedError());
|
|
681
|
+
const requestId = this.requestId.next();
|
|
682
|
+
return new Promise((resolve, reject) => {
|
|
683
|
+
this.pendingRequests.set(requestId, {
|
|
684
|
+
resolve,
|
|
685
|
+
reject
|
|
686
|
+
});
|
|
687
|
+
try {
|
|
688
|
+
start(requestId);
|
|
689
|
+
} catch (err) {
|
|
690
|
+
this.pendingRequests.delete(requestId);
|
|
691
|
+
reject(err);
|
|
692
|
+
}
|
|
693
|
+
});
|
|
694
|
+
}
|
|
695
|
+
drain() {
|
|
696
|
+
if (this.disposed) return;
|
|
697
|
+
this.clearTimeout();
|
|
698
|
+
try {
|
|
699
|
+
for (let i = 0; i < Queue.LIMIT_PER_TICK; i++) if (!this.poll()) break;
|
|
700
|
+
} catch (err) {
|
|
701
|
+
this.emitter.emit("error", err);
|
|
702
|
+
}
|
|
703
|
+
this.timer = setTimeout(() => this.drain(), Queue.POLL_INTERVAL);
|
|
704
|
+
}
|
|
705
|
+
poll() {
|
|
706
|
+
const out = allocBuffer();
|
|
707
|
+
const status = this.functions.nil_ffi_poll(out);
|
|
708
|
+
if (status === ffi_Status.ERR_NOTHING_TO_POLL) return false;
|
|
709
|
+
if (status !== ffi_Status.OK) throw NilError.fromStatus(status);
|
|
710
|
+
const ptr = readBufferPtr(out);
|
|
711
|
+
if (ptr === 0n) throw new Error("failed to poll: unexpected null pointer");
|
|
712
|
+
try {
|
|
713
|
+
const json = ffi.toString(ptr) ?? "";
|
|
714
|
+
const entry = JSON.parse(json);
|
|
715
|
+
this.dispatch(entry);
|
|
716
|
+
} finally {
|
|
717
|
+
this.functions.nil_ffi_free_str(ptr);
|
|
718
|
+
}
|
|
719
|
+
return true;
|
|
720
|
+
}
|
|
721
|
+
dispatch(entry) {
|
|
722
|
+
switch (entry.kind) {
|
|
723
|
+
case "response": {
|
|
724
|
+
const response = JSON.parse(entry.json_str);
|
|
725
|
+
this.emitter.emit("response", response);
|
|
726
|
+
break;
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
rejectAll(error) {
|
|
731
|
+
this.clearTimeout();
|
|
732
|
+
this.pendingRequests.values().forEach(({ reject }) => reject(error));
|
|
733
|
+
this.pendingRequests.clear();
|
|
734
|
+
}
|
|
735
|
+
clearTimeout() {
|
|
736
|
+
if (!isNil(this.timer)) {
|
|
737
|
+
clearTimeout(this.timer);
|
|
738
|
+
this.timer = null;
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
onResponse(response) {
|
|
742
|
+
const pending = this.pendingRequests.get(response.id);
|
|
743
|
+
if (pending) {
|
|
744
|
+
this.pendingRequests.delete(response.id);
|
|
745
|
+
if (response.kind === "ok") pending.resolve(response.data);
|
|
746
|
+
else {
|
|
747
|
+
const { status, error } = response;
|
|
748
|
+
pending.reject(new NilError(error, { status }));
|
|
749
|
+
}
|
|
750
|
+
} else this.emitter.emit("error", new UnknownResponseError(response));
|
|
751
|
+
}
|
|
752
|
+
};
|
|
753
|
+
//#endregion
|
|
754
|
+
//#region package.json
|
|
755
|
+
var version = "0.7.4";
|
|
756
|
+
//#endregion
|
|
757
|
+
//#region \0@oxc-project+runtime@0.139.0/helpers/esm/decorate.js
|
|
758
|
+
function __decorate(decorators, target, key, desc) {
|
|
759
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
760
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
761
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
762
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
763
|
+
}
|
|
764
|
+
//#endregion
|
|
727
765
|
//#region src/index.ts
|
|
728
766
|
//! Copyright (C) Call of Nil contributors
|
|
729
767
|
//! SPDX-License-Identifier: AGPL-3.0-only
|
|
730
|
-
var VERSION =
|
|
768
|
+
var VERSION = version;
|
|
731
769
|
var USER_AGENT = `nil-ffi-node/${VERSION}`;
|
|
732
770
|
var Nil = class Nil {
|
|
733
|
-
constructor(dll) {
|
|
771
|
+
constructor(dll, options) {
|
|
734
772
|
this.disposed = false;
|
|
735
773
|
if (!dll.endsWith(ffi.suffix)) dll = `${dll}.${ffi.suffix}`;
|
|
774
|
+
this.path = resolve(dll);
|
|
736
775
|
this.handle = ffi.dlopen(dll, definitions);
|
|
737
776
|
this.functions = this.handle.functions;
|
|
738
|
-
this.queue = new Queue(this.handle);
|
|
777
|
+
this.queue = new Queue(this.handle, options);
|
|
739
778
|
}
|
|
740
|
-
static async init(dll) {
|
|
741
|
-
const nil = new Nil(dll);
|
|
779
|
+
static async init(dll, options) {
|
|
780
|
+
const nil = new Nil(dll, options);
|
|
742
781
|
await nil.setUserAgent(USER_AGENT);
|
|
743
782
|
return nil;
|
|
744
783
|
}
|
|
784
|
+
static async initLatest(outDir, options) {
|
|
785
|
+
const dll = await this.downloadLatest(outDir);
|
|
786
|
+
return this.init(dll, options);
|
|
787
|
+
}
|
|
788
|
+
static async downloadLatest(outDir) {
|
|
789
|
+
const file = `libnil.${ffi.suffix}`;
|
|
790
|
+
const response = await fetch(`https://tsukilabs.dev.br/${file}`);
|
|
791
|
+
if (!response.ok) throw new Error(`failed to download ${file}: ${response.status} ${response.statusText}`);
|
|
792
|
+
const bytes = await response.bytes();
|
|
793
|
+
const path = join(outDir ?? cwd(), file);
|
|
794
|
+
await writeFile(path, bytes);
|
|
795
|
+
return resolve(path);
|
|
796
|
+
}
|
|
745
797
|
[Symbol.dispose]() {
|
|
746
798
|
this.close();
|
|
747
799
|
}
|
|
@@ -752,58 +804,190 @@ var Nil = class Nil {
|
|
|
752
804
|
this.functions.nil_ffi_shutdown();
|
|
753
805
|
this.handle[Symbol.dispose]();
|
|
754
806
|
}
|
|
807
|
+
isClosed() {
|
|
808
|
+
return this.disposed;
|
|
809
|
+
}
|
|
810
|
+
async authorize(req) {
|
|
811
|
+
return this.queue.request((requestId) => {
|
|
812
|
+
this.functions.nil_authorize(requestId, JSON.stringify(req));
|
|
813
|
+
});
|
|
814
|
+
}
|
|
755
815
|
async getClientVersion() {
|
|
756
|
-
this.throwIfClosed();
|
|
757
816
|
return this.queue.request((requestId) => {
|
|
758
817
|
this.functions.nil_client_version(requestId);
|
|
759
818
|
});
|
|
760
819
|
}
|
|
761
820
|
async getFfiVersion() {
|
|
762
|
-
this.throwIfClosed();
|
|
763
821
|
return this.queue.request((requestId) => {
|
|
764
822
|
this.functions.nil_ffi_version(requestId);
|
|
765
823
|
});
|
|
766
824
|
}
|
|
825
|
+
async getPlayer(req) {
|
|
826
|
+
return this.queue.request((requestId) => {
|
|
827
|
+
this.functions.nil_get_player(requestId, JSON.stringify(req));
|
|
828
|
+
});
|
|
829
|
+
}
|
|
830
|
+
async getPlayerIds(req) {
|
|
831
|
+
return this.queue.request((requestId) => {
|
|
832
|
+
this.functions.nil_get_player_ids(requestId, JSON.stringify(req));
|
|
833
|
+
});
|
|
834
|
+
}
|
|
835
|
+
async getPlayerStatus(req) {
|
|
836
|
+
return this.queue.request((requestId) => {
|
|
837
|
+
this.functions.nil_get_player_status(requestId, JSON.stringify(req));
|
|
838
|
+
});
|
|
839
|
+
}
|
|
767
840
|
async getRemoteWorldLimit() {
|
|
768
|
-
this.throwIfClosed();
|
|
769
841
|
return this.queue.request((requestId) => {
|
|
770
842
|
this.functions.nil_get_remote_world_limit(requestId);
|
|
771
843
|
});
|
|
772
844
|
}
|
|
773
845
|
async getRemoteWorldLimitPerUser() {
|
|
774
|
-
this.throwIfClosed();
|
|
775
846
|
return this.queue.request((requestId) => {
|
|
776
847
|
this.functions.nil_get_remote_world_limit_per_user(requestId);
|
|
777
848
|
});
|
|
778
849
|
}
|
|
779
850
|
async getRemoteWorlds() {
|
|
780
|
-
this.throwIfClosed();
|
|
781
851
|
return this.queue.request((requestId) => {
|
|
782
852
|
this.functions.nil_get_remote_worlds(requestId);
|
|
783
853
|
});
|
|
784
854
|
}
|
|
855
|
+
async getRuntimeNumAliveTasks() {
|
|
856
|
+
return this.queue.request((requestId) => {
|
|
857
|
+
this.functions.nil_runtime_num_alive_tasks(requestId);
|
|
858
|
+
});
|
|
859
|
+
}
|
|
860
|
+
async getRuntimeNumWorkers() {
|
|
861
|
+
return this.queue.request((requestId) => {
|
|
862
|
+
this.functions.nil_runtime_num_workers(requestId);
|
|
863
|
+
});
|
|
864
|
+
}
|
|
865
|
+
async getServerAddr() {
|
|
866
|
+
return this.queue.request((requestId) => {
|
|
867
|
+
this.functions.nil_server_addr(requestId);
|
|
868
|
+
});
|
|
869
|
+
}
|
|
870
|
+
async getServerKind() {
|
|
871
|
+
return this.queue.request((requestId) => {
|
|
872
|
+
this.functions.nil_get_server_kind(requestId);
|
|
873
|
+
});
|
|
874
|
+
}
|
|
785
875
|
async getUserAgent() {
|
|
786
|
-
this.throwIfClosed();
|
|
787
876
|
return this.queue.request((requestId) => {
|
|
788
877
|
this.functions.nil_user_agent(requestId);
|
|
789
878
|
});
|
|
790
879
|
}
|
|
791
|
-
async
|
|
792
|
-
this.throwIfClosed();
|
|
880
|
+
async getWorldId() {
|
|
793
881
|
return this.queue.request((requestId) => {
|
|
794
882
|
this.functions.nil_world(requestId);
|
|
795
883
|
});
|
|
796
884
|
}
|
|
885
|
+
async isHost() {
|
|
886
|
+
return this.queue.request((requestId) => {
|
|
887
|
+
this.functions.nil_is_host(requestId);
|
|
888
|
+
});
|
|
889
|
+
}
|
|
890
|
+
async isLocal() {
|
|
891
|
+
return this.queue.request((requestId) => {
|
|
892
|
+
this.functions.nil_is_local(requestId);
|
|
893
|
+
});
|
|
894
|
+
}
|
|
895
|
+
async isReady() {
|
|
896
|
+
return this.queue.request((requestId) => {
|
|
897
|
+
this.functions.nil_is_ready(requestId);
|
|
898
|
+
});
|
|
899
|
+
}
|
|
900
|
+
async isRemote() {
|
|
901
|
+
return this.queue.request((requestId) => {
|
|
902
|
+
this.functions.nil_is_remote(requestId);
|
|
903
|
+
});
|
|
904
|
+
}
|
|
905
|
+
async playerExists(req) {
|
|
906
|
+
return this.queue.request((requestId) => {
|
|
907
|
+
this.functions.nil_player_exists(requestId, JSON.stringify(req));
|
|
908
|
+
});
|
|
909
|
+
}
|
|
910
|
+
async setPlayerStatus(req) {
|
|
911
|
+
return this.queue.request((requestId) => {
|
|
912
|
+
this.functions.nil_set_player_status(requestId, JSON.stringify(req));
|
|
913
|
+
});
|
|
914
|
+
}
|
|
797
915
|
async setUserAgent(userAgent) {
|
|
798
|
-
this.
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
916
|
+
return this.queue.request((requestId) => {
|
|
917
|
+
this.functions.nil_set_user_agent(requestId, JSON.stringify(userAgent));
|
|
918
|
+
});
|
|
919
|
+
}
|
|
920
|
+
async spawnPlayer(req) {
|
|
921
|
+
return this.queue.request((requestId) => {
|
|
922
|
+
this.functions.nil_spawn_player(requestId, JSON.stringify(req));
|
|
802
923
|
});
|
|
803
924
|
}
|
|
804
|
-
|
|
805
|
-
|
|
925
|
+
async startServer(options) {
|
|
926
|
+
return this.queue.request((requestId) => {
|
|
927
|
+
this.functions.nil_start_server(requestId, JSON.stringify(options));
|
|
928
|
+
});
|
|
929
|
+
}
|
|
930
|
+
async startServerWithSavedata(path) {
|
|
931
|
+
return this.queue.request((requestId) => {
|
|
932
|
+
this.functions.nil_start_server_with_savedata(requestId, JSON.stringify(path));
|
|
933
|
+
});
|
|
934
|
+
}
|
|
935
|
+
async stopClient() {
|
|
936
|
+
return this.queue.request((requestId) => {
|
|
937
|
+
this.functions.nil_stop_client(requestId);
|
|
938
|
+
});
|
|
939
|
+
}
|
|
940
|
+
async stopServer() {
|
|
941
|
+
return this.queue.request((requestId) => {
|
|
942
|
+
this.functions.nil_stop_server(requestId);
|
|
943
|
+
});
|
|
944
|
+
}
|
|
945
|
+
async updateClient(options) {
|
|
946
|
+
return this.queue.request((requestId) => {
|
|
947
|
+
this.functions.nil_update_client(requestId, JSON.stringify(options));
|
|
948
|
+
});
|
|
949
|
+
}
|
|
950
|
+
async validateToken(req) {
|
|
951
|
+
return this.queue.request((requestId) => {
|
|
952
|
+
this.functions.nil_validate_token(requestId, JSON.stringify(req));
|
|
953
|
+
});
|
|
806
954
|
}
|
|
807
955
|
};
|
|
956
|
+
__decorate([ThrowIfClosed], Nil.prototype, "authorize", null);
|
|
957
|
+
__decorate([ThrowIfClosed], Nil.prototype, "getClientVersion", null);
|
|
958
|
+
__decorate([ThrowIfClosed], Nil.prototype, "getFfiVersion", null);
|
|
959
|
+
__decorate([ThrowIfClosed], Nil.prototype, "getPlayer", null);
|
|
960
|
+
__decorate([ThrowIfClosed], Nil.prototype, "getPlayerIds", null);
|
|
961
|
+
__decorate([ThrowIfClosed], Nil.prototype, "getPlayerStatus", null);
|
|
962
|
+
__decorate([ThrowIfClosed], Nil.prototype, "getRemoteWorldLimit", null);
|
|
963
|
+
__decorate([ThrowIfClosed], Nil.prototype, "getRemoteWorldLimitPerUser", null);
|
|
964
|
+
__decorate([ThrowIfClosed], Nil.prototype, "getRemoteWorlds", null);
|
|
965
|
+
__decorate([ThrowIfClosed], Nil.prototype, "getRuntimeNumAliveTasks", null);
|
|
966
|
+
__decorate([ThrowIfClosed], Nil.prototype, "getRuntimeNumWorkers", null);
|
|
967
|
+
__decorate([ThrowIfClosed], Nil.prototype, "getServerAddr", null);
|
|
968
|
+
__decorate([ThrowIfClosed], Nil.prototype, "getServerKind", null);
|
|
969
|
+
__decorate([ThrowIfClosed], Nil.prototype, "getUserAgent", null);
|
|
970
|
+
__decorate([ThrowIfClosed], Nil.prototype, "getWorldId", null);
|
|
971
|
+
__decorate([ThrowIfClosed], Nil.prototype, "isHost", null);
|
|
972
|
+
__decorate([ThrowIfClosed], Nil.prototype, "isLocal", null);
|
|
973
|
+
__decorate([ThrowIfClosed], Nil.prototype, "isReady", null);
|
|
974
|
+
__decorate([ThrowIfClosed], Nil.prototype, "isRemote", null);
|
|
975
|
+
__decorate([ThrowIfClosed], Nil.prototype, "playerExists", null);
|
|
976
|
+
__decorate([ThrowIfClosed], Nil.prototype, "setPlayerStatus", null);
|
|
977
|
+
__decorate([ThrowIfClosed], Nil.prototype, "setUserAgent", null);
|
|
978
|
+
__decorate([ThrowIfClosed], Nil.prototype, "spawnPlayer", null);
|
|
979
|
+
__decorate([ThrowIfClosed], Nil.prototype, "startServer", null);
|
|
980
|
+
__decorate([ThrowIfClosed], Nil.prototype, "startServerWithSavedata", null);
|
|
981
|
+
__decorate([ThrowIfClosed], Nil.prototype, "stopClient", null);
|
|
982
|
+
__decorate([ThrowIfClosed], Nil.prototype, "stopServer", null);
|
|
983
|
+
__decorate([ThrowIfClosed], Nil.prototype, "updateClient", null);
|
|
984
|
+
__decorate([ThrowIfClosed], Nil.prototype, "validateToken", null);
|
|
985
|
+
function ThrowIfClosed(_target, _key, descriptor) {
|
|
986
|
+
const method = descriptor.value;
|
|
987
|
+
descriptor.value = function(...args) {
|
|
988
|
+
if (this.isClosed()) throw new HandleClosedError();
|
|
989
|
+
return Reflect.apply(method, this, args);
|
|
990
|
+
};
|
|
991
|
+
}
|
|
808
992
|
//#endregion
|
|
809
993
|
export { HandleClosedError, Nil, NilError, USER_AGENT, UnknownResponseError, VERSION, definitions };
|
package/dist/queue.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Handle } from '.';
|
|
2
|
-
import { Option } from '@tb-dev/utils';
|
|
2
|
+
import { nil, Option } from '@tb-dev/utils';
|
|
3
3
|
import { ffi_RequestId } from '@tsukilabs/nil-bindings';
|
|
4
4
|
export declare class Queue implements Disposable {
|
|
5
5
|
private static readonly POLL_INTERVAL;
|
|
@@ -13,7 +13,7 @@ export declare class Queue implements Disposable {
|
|
|
13
13
|
private readonly errorHandler;
|
|
14
14
|
constructor(handle: Handle, options?: QueueOptions);
|
|
15
15
|
[Symbol.dispose](): void;
|
|
16
|
-
request<T =
|
|
16
|
+
request<T = nil>(start: RequestFn): Promise<T>;
|
|
17
17
|
private drain;
|
|
18
18
|
private poll;
|
|
19
19
|
private dispatch;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tsukilabs/nil-ffi",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.4",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"packageManager": "pnpm@11.17.0",
|
|
6
6
|
"license": "AGPL-3.0-only",
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
},
|
|
30
30
|
"dependencies": {
|
|
31
31
|
"@tb-dev/utils": "^8.1.1",
|
|
32
|
-
"@tsukilabs/nil-bindings": "
|
|
32
|
+
"@tsukilabs/nil-bindings": "^0.7.3",
|
|
33
33
|
"es-toolkit": "^1.50.0"
|
|
34
34
|
},
|
|
35
35
|
"files": ["dist"],
|