@tendrock/ipc
Version:
An IPC system implementation of Minecraft Community Script Protocol for Minecraft Bedrock
283 lines (282 loc) • 11.8 kB
JavaScript
import { system } from "@minecraft/server";
import { IpcPacketType, IpcVersion } from "../lib";
import { AsyncUtils } from "../util/AsyncUtils";
import { Serializers } from "../ref";
export class IpcV1 {
constructor(scriptEnv) {
this._serializer = Serializers.V1;
this.scriptEnv = scriptEnv;
}
static register(identifier, uuid) {
return new IpcV1({ identifier, uuid });
}
postMessage(message, options) {
const id = this._serializer.serializeToScriptEventId(options);
system.sendScriptEvent(id, message);
}
postMessagePieces(messages, options) {
messages.forEach((message, index) => {
options.metadata.packet_number = messages.length - 1 - index;
const id = this._serializer.serializeToScriptEventId(options);
system.sendScriptEvent(id, message);
});
}
postByParamOptions(value, options, messages) {
messages !== null && messages !== void 0 ? messages : (messages = this._serializer.serializeData(value, options.metadata.encoding));
if (typeof messages === 'string') {
this.postMessage(messages, options);
}
else {
this.postMessagePieces(messages, options);
}
}
post(packetType, identifier, value, targetEnvId) {
this.postByParamOptions(value, {
senderEnvId: this.scriptEnv.identifier, targetEnvId,
header: { version: IpcVersion.V1 },
metadata: {
packet_type: packetType, packet_id: identifier
}
});
}
postToAll(packetType, identifier, value, targetEnvIdList) {
const messages = this._serializer.serializeData(value, 'json');
const options = {
senderEnvId: this.scriptEnv.identifier,
header: { version: IpcVersion.V1 },
metadata: {
packet_type: packetType, packet_id: identifier
},
targetEnvId: undefined
};
if (typeof messages === 'string') {
this._serializer.serializeAllToScriptEventId(targetEnvIdList, options).forEach((id) => system.sendScriptEvent(id, messages));
}
else {
targetEnvIdList.forEach((targetEnvId) => {
options.targetEnvId = targetEnvId;
this.postMessagePieces(messages, options);
});
}
}
listenScriptEvent(listener) {
const thisEnvId = this.scriptEnv.identifier;
const scriptEvenCallback = system.afterEvents.scriptEventReceive.subscribe((event) => {
const { id, message } = event;
if (!id)
return;
const { headerStr, metadataStr, senderEnvId, targetEnvId } = this._serializer.deserializeScriptEventId(id);
if (!senderEnvId)
return;
listener({ headerStr, metadataStr, senderEnvId, targetEnvId, message });
}, { namespaces: [thisEnvId, IpcV1.BroadcastEnvId] });
return () => {
system.afterEvents.scriptEventReceive.unsubscribe(scriptEvenCallback);
};
}
assertNotBroadcastEnvId(targetEnvId, errorMessage) {
if (!targetEnvId)
return;
if (targetEnvId === IpcV1.BroadcastEnvId) {
throw new Error(`${errorMessage}! Env id can not be Broadcast Env Id`);
}
}
assertNotIncludeBroadcastEnvId(targetEnvIds, errorMessage) {
if (!targetEnvIds)
return;
if (targetEnvIds.includes(IpcV1.BroadcastEnvId)) {
throw new Error(`${errorMessage}! Env id list can not include Broadcast Env Id`);
}
}
assertNotBeOrIncludeBroadcastEnvId(targetEnvIds, errorMessage) {
if (typeof targetEnvIds === "string") {
this.assertNotBroadcastEnvId(targetEnvIds, errorMessage);
}
else {
this.assertNotIncludeBroadcastEnvId(targetEnvIds, errorMessage);
}
}
send(identifier, value, targetEnvIds) {
this.assertNotBeOrIncludeBroadcastEnvId(targetEnvIds, "Send message failed");
if (typeof targetEnvIds === "string") {
this.post(IpcPacketType.Message, identifier, value, targetEnvIds);
return;
}
if (targetEnvIds.length === 0)
return;
this.postToAll(IpcPacketType.Message, identifier, value, targetEnvIds);
}
broadcast(identifier, value) {
this.post(IpcPacketType.Message, identifier, value, IpcV1.BroadcastEnvId);
}
_mergeMessagePackets(messagePackets) {
return messagePackets.reduce((acc, cur) => acc + cur);
}
_getFullMessage(message, metadata, msgPackets) {
if (metadata.packet_number === undefined) {
return message;
}
msgPackets.push(message);
if (metadata.packet_number === 0) {
return this._mergeMessagePackets(msgPackets);
}
else {
return undefined;
}
}
deserializeMetadata(metadataStr, packetType, packetId) {
const metadata = this._serializer.deserializeMetadata(metadataStr);
if (metadata.packet_type !== packetType)
return { metadata, skipByMetadata: true };
if (!metadata.packet_id || metadata.packet_id !== packetId)
return { metadata, skipByMetadata: true };
return { metadata, skipByMetadata: false };
}
deserializeHeader(headerStr, version) {
const header = this._serializer.deserializeHeader(headerStr);
if (header.version !== version)
return { header, skipByHeader: true };
return { header, skipByHeader: false };
}
on(identifier, listener) {
const msgPackets = [];
return this.listenScriptEvent((event) => {
const { senderEnvId, headerStr, metadataStr, message } = event;
// console.log(`on message: ${senderEnvId} ${metadataStr} ${message}`);
const header = this._serializer.deserializeHeader(headerStr);
if (header.version !== IpcVersion.V1)
return;
const metadata = this._serializer.deserializeMetadata(metadataStr);
if (metadata.packet_type !== IpcPacketType.Message)
return;
if (!metadata.packet_id || metadata.packet_id !== identifier)
return;
const fullMessage = this._getFullMessage(message, metadata, msgPackets);
if (fullMessage === undefined)
return;
listener({
packetId: metadata.packet_id,
value: this._serializer.deserializeData(fullMessage, metadata.encoding),
senderEnvId
});
msgPackets.length = 0;
});
}
once(identifier, listener) {
const dispose = this.on(identifier, (event) => {
listener(event);
dispose();
});
return dispose;
}
debounce(identifier, listener, options) {
const { merge, leading = false, delayTicks } = options;
let lastEvent;
let timeoutId;
const trigger = (event) => {
listener(event);
lastEvent = undefined;
};
this.on(identifier, (event) => {
const currentEvent = merge(event, lastEvent);
if (leading && !timeoutId) {
trigger(currentEvent);
}
else {
if (timeoutId) {
system.clearRun(timeoutId);
}
timeoutId = system.runTimeout(() => {
trigger(currentEvent);
timeoutId = undefined;
}, delayTicks);
lastEvent = currentEvent;
}
});
return this;
}
invoke(identifier, value, targetEnvIds) {
this.assertNotBeOrIncludeBroadcastEnvId(targetEnvIds, "Invoke method failed");
const result = new Promise((resolve, reject) => {
const thisEnvId = this.scriptEnv.identifier;
let resolveTimes = 0;
const invokeResults = [];
const msgPackets = [];
const scriptEvenCallback = system.afterEvents.scriptEventReceive.subscribe((event) => {
const { id, message } = event;
if (!id)
return;
const { headerStr, metadataStr, senderEnvId } = this._serializer.deserializeScriptEventId(id);
if (!senderEnvId)
return;
const { header, skipByHeader } = this.deserializeHeader(headerStr, IpcVersion.V1);
if (skipByHeader)
return;
const { metadata, skipByMetadata } = this.deserializeMetadata(metadataStr, IpcPacketType.InvokeResult, identifier);
if (skipByMetadata)
return;
const fullMessage = this._getFullMessage(message, metadata, msgPackets);
if (fullMessage === undefined)
return;
const resolvePromise = (results) => {
resolve(results);
msgPackets.length = 0;
system.afterEvents.scriptEventReceive.unsubscribe(scriptEvenCallback);
};
const result = {
value: this._serializer.deserializeData(fullMessage, metadata.encoding),
envId: senderEnvId
};
if (typeof targetEnvIds === 'string') {
resolvePromise(result);
}
else {
invokeResults.push(result);
}
resolveTimes++;
if (resolveTimes >= targetEnvIds.length) {
resolvePromise(invokeResults);
}
}, { namespaces: [thisEnvId, IpcV1.BroadcastEnvId] });
});
if (typeof targetEnvIds === "string") {
this.post(IpcPacketType.Invoke, identifier, value, targetEnvIds);
}
else {
this.postToAll(IpcPacketType.Invoke, identifier, value, targetEnvIds);
}
return result;
}
handle(identifier, listener, senderEnvFilter) {
this.assertNotBeOrIncludeBroadcastEnvId(senderEnvFilter, "Handle method invoke failed");
const thisEnvId = this.scriptEnv.identifier;
const msgPackets = [];
return this.listenScriptEvent((event) => {
const { senderEnvId, headerStr, metadataStr, message } = event;
if (senderEnvFilter && !senderEnvFilter.includes(senderEnvId))
return;
const { header, skipByHeader } = this.deserializeHeader(headerStr, IpcVersion.V1);
if (skipByHeader)
return;
const { metadata, skipByMetadata } = this.deserializeMetadata(metadataStr, IpcPacketType.Invoke, identifier);
if (skipByMetadata)
return;
const fullMessage = this._getFullMessage(message, metadata, msgPackets);
if (fullMessage === undefined)
return;
const invokeRetPromise = AsyncUtils.awaitIfAsync(listener(this._serializer.deserializeData(fullMessage, metadata.encoding)));
invokeRetPromise.then((retValue) => {
this.postByParamOptions(retValue, {
senderEnvId: thisEnvId, targetEnvId: senderEnvId,
header: { version: IpcVersion.V1 },
metadata: {
packet_type: IpcPacketType.InvokeResult, packet_id: identifier
}
});
});
msgPackets.length = 0;
});
}
;
}
IpcV1.BroadcastEnvId = 'ipc_broadcast';