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