[Unity] 在puerts中使用js多线程(伪)

版本信息:

puerts版本: unity_plugin_v9        javascript引擎: v8

unity版本: 2019.4.8f1

本文开源: https://github.com/layerpsr/worker_for_unity_ts

大致思路: 

  1. 每个线程拥有自己的JsEnv实例;
  2. 线程之间通过JsWorker.cs实例传递数据丶消息;
  3. 如果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
Unity使用多线程可以提高游戏的性能和响应速度。但需要注意的是,Unity的所有API都是线程不安全的,所以在使用多线程时,必须采用正确的方式来访问Unity API。 以下是在Unity使用多线程的步骤: 1. 创建新的线程 使用C#的Thread类创建新的线程,如下所示: ``` Thread thread = new Thread(ThreadMethod); thread.Start(); ``` 其,`ThreadMethod`是新线程要执行的方法。 2. 在新线程执行逻辑 在新线程执行复杂的计算或其他需要耗费时间的操作。需要注意的是,在新线程不能直接访问Unity API。 3. 使用线程安全的方式访问Unity API 为了避免访问Unity API时出现线程安全问题,可以使用以下方法: - 使用线程安全的类型,如ConcurrentQueue,来存储需要在主线程处理的数据。 - 使用Unity的主线程调用方法,如`UnityMainThreadDispatcher.Instance().Enqueue()`方法,将需要在主线程执行的代码添加到主线程的执行队列。 以下是一个使用多线程的示例代码: ``` private ConcurrentQueue<float> queue = new ConcurrentQueue<float>(); private void Update() { float value; while (queue.TryDequeue(out value)) { // 在主线程处理数据 Debug.Log(value); } } private void ThreadMethod() { for (float i = 0; i < 10000; i++) { queue.Enqueue(i); } } ``` 在上面的示例代码,我们使用ConcurrentQueue存储需要在主线程处理的数据。然后在Update方法不断地尝试从队列取出数据并在主线程处理。在新线程,我们向队列添加数据。这样可以保证在主线程处理数据,避免了访问Unity API时出现线程安全问题。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值