版本信息:
puerts版本: unity_plugin_v9 javascript引擎: v8
unity版本: 2019.4.8f1
大致思路:
- 每个线程拥有自己的JsEnv实例;
- 线程之间通过JsWorker.cs实例传递数据丶消息;
- 如果js object想要传给其他线程, 需要将它封装为C# object让JsWorker.cs进行分发;
对象的传递:
- 线程1将js object封装为Package.cs对象(仅允许js值类型拷贝和C# object的引用);
- 线程1将封装的Package.cs对象通过JsWorker.cs分发给线程2;
- 线程2从JsWorker.cs拿到Package.cs对象, 在js拆包还原为js object;
注: 建议仅传递纯数据, 原因如下:
- js object的原型链无法进行传递;
- js function将序列化为字符串, 使用eval()还原, 闭包引用无法传递 ;
例子代码:
/**
* main.ts文件
*/
import * as CS from "csharp";
const id = CS.System.Threading.Thread.CurrentThread.ManagedThreadId;
const threadName = `Main(${id})\t`;
//此处为实现的Puerts.ILoader类
let worker = new JsWorker(CS.JsManager.GetInstance().Loader);
worker.on("main_on", () => "this is main thread");
worker.on("data", data => {
if (typeof data == "function") {
console.log(threadName, data.toString());
}
else if (typeof data == "object") {
console.log(threadName, JSON.stringify(data));
}
else
console.log(threadName, data);
});
worker.start("./test");
worker.post("data", { msg: "this main thread message" });
//JsWorker在JsEnv初始化时需要主线程调用, 所以此处不能调用阻塞方法
setTimeout(() => {
console.log(threadName, worker.postSync("child_on"))
}, 1000);
/**
* test.ts文件
*/
import * as CS from "csharp";
const id = CS.System.Threading.Thread.CurrentThread.ManagedThreadId;
const threadName = `Child(${id})\t`;
globalWorker.on("child_on", () => "this is child thread");
globalWorker.on("data", data => {
if (typeof data == "function") {
console.log(threadName, data.toString());
}
else if (typeof data == "object") {
console.log(threadName, JSON.stringify(data));
}
else
console.log(threadName, data);
});
globalWorker.post("data", { msg: "this child thread message" });
console.log(threadName, globalWorker.postSync("main_on"));
setTimeout(() => {
let i = 3;
while (i-- > 0) {
globalWorker.post("data", { msg: "this is child thread message", index: i });
CS.System.Threading.Thread.Sleep(1000);
}
}, 1000);
打印日志如下:
关键代码:
/**
* jsWorker.ts
*/
import * as CS from "csharp";
import { $generic } from "puerts";
let List = $generic(CS.System.Collections.Generic.List$1, CS.System.Object);
const CLOSE_EVENT = "close";
class JsWorker {
public get isAlive() { return this.worker.IsAlive; }
public readonly isMain: boolean;
private readonly worker: CS.JsWorker;
private readonly callbacks: Map<string, ((data?: any) => void)[]>;
constructor(loader: CS.Puerts.ILoader | CS.JsWorker) {
let worker: CS.JsWorker = undefined;
if (loader instanceof CS.JsWorker) {
worker = loader;
this.isMain = false;
} else {
worker = CS.JsWorker.New(loader);
this.isMain = true;
}
this.worker = worker;
this.callbacks = new Map();
this.working();
}
private working() {
let getValue = (data: CS.JsWorker.Package) => {
if (data !== undefined && data !== null && data !== void 0) {
return this.unpackage(data);
}
return undefined;
};
let onmessage = (name: string, data: CS.JsWorker.Package): CS.JsWorker.Package => {
let result = undefined;
let arr = this.callbacks.get(name);
if (arr) {
let o = getValue(data);
for (let cb of arr) {
result = cb(o);
}
}
if (result !== undefined && result !== null && result !== void 0)
return this.package(result);
return undefined;
};
if (this.isMain)
this.worker.messageByMain = (name, data) => {
if (name === CLOSE_EVENT) {
let o = getValue(data), closing = true;
let arr = this.callbacks.get(name);
if (arr)
arr.forEach(cb => {
if ((cb as (data?: any) => boolean)(o) === false)
closing = false;
});
if (closing)
this.dispose();
return this.package(closing);
} else
return onmessage(name, data);
};
else
this.worker.messageByChild = onmessage;
}
private package(data: any, refs?: WeakMap<object, number>, refId?: number): CS.JsWorker.Package {
refId = refId ?? 1;
refs = refs ?? new WeakMap();
let result = new CS.JsWorker.Package();
let type = typeof (data);
if ((type === "object" || type === "function") && refs.has(data)) {
result.type = CS.JsWorker.Type.RefObject;
result.value = refs.get(data);
}
else {
switch (type) {
case "object":
{
//添加引用
let id = refId++;
result.id = id;
refs.set(data, id);
//创建C#对象
if (data instanceof CS.System.Object) {
result.type = CS.JsWorker.Type.Value;
result.value = data;
}
else if (data instanceof ArrayBuffer) {
result.type = CS.JsWorker.Type.ArrayBuffer;
result.value = CS.JsWorker.Package.ToBytes(data);
}
else if (Array.isArray(data)) {
let list = new List() as CS.System.Co