大致思路:
- 每个线程拥有自己的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.Collections.Generic.List$1<any>;
for (let i = 0; i < data.length; i++) {
let item = this.package(data[i], refs, refId);
item.info = i;
list.Add(item);
}
result.type = CS.JsWorker.Type.Array;
result.value = list;
}
else {
let list = new List() as CS.System.Collections.Generic.List$1<any>;
Object.keys(data).forEach(key => {
let item = this.package(data[key], refs, refId);
item.info = key;
list.Add(item);
});
result.type = CS.JsWorker.Type.Object;
result.value = list;
}
}
break;
case "function":
{
//添加引用
let id = refId++;
result.id = id;
refs.set(data, id);
//创建C#对象
result.type = CS.JsWorker.Type.Function;
result.value = data.toString();
}
break;
case "string":
case "number":
case "bigint":
case "boolean":
result.type = CS.JsWorker.Type.Value;
result.value = data;
break;
default:
result.type = CS.JsWorker.Type.Unknown;
break;
}
}
return result;
}
private unpackage(data: CS.JsWorker.Package, refs?: Map<number, Object>): any {
refs = refs ?? new Map();
let result = undefined, id = data.id, value = data.value;
switch (data.type) {
case CS.JsWorker.Type.Object:
{
result = {};
if (id > 0) refs.set(id, result); //Add ref object
let arr = value as CS.System.Collections.Generic.List$1<CS.JsWorker.Package>;
for (let i = 0; i < arr.Count; i++) {
let item = arr.get_Item(i);
result[item.info] = this.unpackage(item, refs);
}
}
break;
case CS.JsWorker.Type.Array:
{
result = [];
if (id > 0) refs.set(id, result); //Add ref object
let arr = value as CS.System.Collections.Generic.List$1<CS.JsWorker.Package>;
for (let i = 0; i < arr.Count; i++) {
let item = arr.get_Item(i);
result[item.info] = this.unpackage(item, refs);
}
}
break;
case CS.JsWorker.Type.ArrayBuffer:
result = CS.JsWorker.Package.ToArrayBuffer(value);
if (id > 0) refs.set(id, result); //Add ref object
break;
case CS.JsWorker.Type.Function:
result = eval(value);
if (id > 0) refs.set(id, result); //Add ref object
break;
case CS.JsWorker.Type.RefObject:
if (refs.has(value))
result = refs.get(value);
else
result = "Error: ref id " + value + " not found";
break;
case CS.JsWorker.Type.Unknown:
default:
result = value;
if (id > 0) refs.set(id, result); //Add ref object
break;
}
return result;
}
public start(filepath: string) {
if (globalWorker && globalWorker["worker"] == this.worker)
throw new Error("Thread cannot called start");
this.worker.Startup(filepath);
}
public dispose() {
if (globalWorker && globalWorker["worker"] == this.worker)
this.post(CLOSE_EVENT);
else {
this.worker.Dispose();
this.callbacks.clear();
}
}
public post(eventName: string, data?: any) {
let o: CS.JsWorker.Package;
if (data !== undefined && data !== null && data !== void 0) {
o = this.package(data);
}
if (this.isMain)
this.worker.CallChild(eventName, o);
else
this.worker.CallMain(eventName, o);
}
public postSync<T>(eventName: string, data?: any): T {
let o: CS.JsWorker.Package, result = undefined;
if (data !== undefined && data !== null && data !== void 0) {
o = this.package(data);
}
if (this.isMain)
result = this.worker.Sync.CallChild(eventName, o);
else
result = this.worker.Sync.CallMain(eventName, o);
//Result
if (result !== undefined && result !== null && result !== void 0) {
result = this.unpackage(result);
}
return result;
}
public eval(chunk: string, chunkName?: string) {
if (globalWorker && globalWorker["worker"] == this.worker)
throw new Error("Thread cannot called eval");
this.worker.Eval(chunk, chunkName);
}
public on(eventName: string, cb: (data?: any) => void) {
if (eventName && cb) {
let arr = this.callbacks.get(eventName);
if (!arr) {
arr = [];
this.callbacks.set(eventName, arr);
}
arr.push(cb);
}
}
public remove(eventName: string, cb: (data?: any) => void) {
let arr = this.callbacks.get(eventName);
if (arr) {
let index = arr.indexOf(cb);
if (index >= 0)
this.callbacks.set(eventName, [...arr.slice(0, index), ...arr.slice(index + 1)]);
}
}
public removeAll(eventName?: string) {
if (eventName)
this.callbacks.delete(eventName);
else
this.callbacks.clear();
}
}
(function () {
let _this = (this ?? globalThis);
_this["JsWorker"] = JsWorker;
_this["globalWorker"] = undefined;
})();
/**
* 接口声明
*/
declare global {
class JsWorker {
public get isAlive(): boolean;
public readonly isMain: boolean;
public constructor(loader: CS.Puerts.ILoader);
/**
* 开始执行脚本(实例生命周期内仅调用一次)
*/
public start(filepath: string): void;
/**
* 关闭JsWorker实例, 不可在内部关闭实例
*/
public dispose(): void;
/**
* 发送一条消息(异步)
*/
public post(eventName: string, data?: any): void;
/**
* 同步发送消息并获取返回值
*/
public postSync<T>(eventName: string, data?: any): T;
/**
* 执行一段代码, 由外部程序调用
*/
public eval(chunk: string, chunkName?: string): void;
/**
* 监听事件信息
*/
public on(eventName: string, cb: (data?: any) => void): void;
/**
* 监听并劫持JsWorker实例close消息
*/
public on(eventName: "close", cb: (state?: any) => boolean): void;
/**
* 移除一条监听
*/
public remove(eventName: string, cb: (data?: any) => void): void;
/**
* 移除所有监听
*/
public removeAll(eventName: string): void;
/**
* 移除所有监听
*/
public removeAll(): void;
}
/**
* 只能在JsWorker线程内部访问, 与主线程交互的对象
*/
const globalWorker: JsWorker;
}
/**
* JsWorker.cs
*/
using System;
using System.Collections.Generic;
using System.Threading;
using Puerts;
using UnityEngine;
public class JsWorker : MonoBehaviour, IDisposable
{
public static JsWorker New(ILoader loader, string filepath)
{
var obj = new GameObject("JsWorker");
DontDestroyOnLoad(obj);
var ins = obj.AddComponent<JsWorker>();
ins.loader = new SyncLoader(ins, loader);
if (!string.IsNullOrEmpty(filepath))
ins.Working(filepath);
return ins;
}
public static JsWorker New(ILoader loader)
{
return New(loader, null);
}
/// <summary>
/// jsWorker.ts脚本
/// </summary>
private const string JS_WORKER = "require('./common/jsWorker')";
/// <summary>
/// 每次处理的事件
/// </summary>
private const int PROCESS_COUNT = 5;
/// <summary>
/// Unity主线程
/// </summary>
private static readonly int MAIN_THREAD_ID = Thread.CurrentThread.ManagedThreadId;
public JsEnv JsEnv { get; private set; }
//消息接口
public Func<string, Package, Package> messageByMain { get; set; }
public Func<string, Package, Package> messageByChild { get; set; }
//线程初始完成, 且运行中
public bool IsAlive
{
get
{
return this.thread != null && this.thread.IsAlive && this.JsEnv != null;
}
}
//同步对象
private SyncLoader loader;
private SyncProcess sync;
public SyncProcess Sync
{
get
{
if (!this.IsAlive)
throw new Exception("Thread not ready ok, can't use it now.");
return this.sync;
}
}
public ReaderWriterLock locker;
private const int lockTimeout = 1000;
//同步状态
private bool syncing;
//线程
private Thread thread;
private bool running = false;
//消息集合
private Queue<Event> mainEvents;
private Queue<Event> childEvents;
//Eval require list
private Queue<(string, string)> eval;
private JsWorker()
{
mainEvents = new Queue<Event>();
childEvents = new Queue<Event>();
eval = new Queue<(string, string)>();
sync = new SyncProcess(this);
locker = new ReaderWriterLock();
}
void Start()
{
if (loader == null)
{
this.enabled = false;
throw new Exception("instance cannot working, loader is null");
}
}
void Update()
{
ProcessMain();
sync.ProcessMain();
loader.ProcessMain();
}
void OnDestroy()
{
Dispose();
}
void Working(string filepath)
{
if (this.JsEnv != null || this.thread != null || this.running)
throw new Exception("Thread is running, cannot start repeatedly!");
if (this.loader == null)
throw new Exception("Thread cannot start working, loader is null!");
if (!this.enabled)
throw new Exception("Thread cannot start working, main thread is disable");
syncing = false;
running = true;
thread = new Thread(new ThreadStart(() =>
{
JsEnv jsEnv = null;
try
{
// JsEnv脚本放在Resource目录下,故ILoader仅允许在主线程调用
// 子线程_SyncLoader接口会阻塞线程, 直到主线程调用ILoader后才会继续执行
// JsEnv初始化时将调用_SyncLoader接口
jsEnv = JsEnv = new JsEnv(loader);
jsEnv.UsingAction<string, string>();
jsEnv.UsingAction<string, Package>();
jsEnv.UsingFunc<string, Package, object>();
jsEnv.UsingFunc<string, Package, Package>();
jsEnv.Eval(JS_WORKER);
jsEnv.Eval<Action<JsWorker>>(@"(function (_w){ (this ?? globalThis)['globalWorker'] = new JsWorker(_w); })")(this);
jsEnv.Eval(string.Format("require(\"{0}\")", filepath));
while (running)
{
if (JsEnv == null)
break;
Thread.Sleep(20);
jsEnv.Tick();
ProcessChild();
ProcessChildEval(jsEnv);
sync.ProcessChild();
}
}
catch (Exception e)
{
UnityEngine.Debug.LogWarning(e.Message);
}
finally
{
jsEnv?.Dispose();
JsEnv = null;
}
}));
thread.IsBackground = true;
thread.Start();
}
public void VerifySafety(bool isMainThread)
{
var id = Thread.CurrentThread.ManagedThreadId;
if (isMainThread && id != MAIN_THREAD_ID || !isMainThread && id == MAIN_THREAD_ID)
throw new Exception("Incorrect in thread");
}
public void Startup(string filepath)
{
Working(filepath);
}
public void Dispose()
{
messageByMain = null;
messageByChild = null;
running = false;
//此处仅通知线程中断, 由线程自行结束(使用Abort阻塞将导致puerts crash)
if (thread != null) thread.Interrupt();
//if (JsEnv != null) JsEnv.Dispose();
JsEnv = null;
thread = null;
}
public void CallMain(string name, Package data)
{
lock (mainEvents)
{
mainEvents.Enqueue(new Event()
{
name = name,
data = data
});
}
}
public void CallChild(string name, Package data)
{
lock (childEvents)
{
childEvents.Enqueue(new Event()
{
name = name,
data = data
});
}
}
public void Eval(string chunk, string chunkName = "chunk")
{
if (chunk == null)
return;
lock (eval)
{
eval.Enqueue((chunk, chunkName));
}
}
private void ProcessMain()
{
if (mainEvents.Count > 0)
{
List<Event> events = new List<Event>();
lock (mainEvents)
{
int count = PROCESS_COUNT;
while (count-- > 0 && mainEvents.Count > 0)
events.Add(mainEvents.Dequeue());
}
Func<string, Package, Package> func = this.messageByMain;
if (func != null)
{
for (int i = 0; i < events.Count; i++)
{
try
{
func(events[i].name, events[i].data);
}
catch (Exception e)
{
UnityEngine.Debug.LogError(e.Message);
}
}
}
}
}
private void ProcessChild()
{
if (childEvents.Count > 0)
{
List<Event> events = new List<Event>();
lock (childEvents)
{
int count = PROCESS_COUNT;
while (count-- > 0 && childEvents.Count > 0)
events.Add(childEvents.Dequeue());
}
Func<string, Package, Package> func = this.messageByChild;
if (func != null)
{
for (int i = 0; i < events.Count; i++)
{
try
{
func(events[i].name, events[i].data);
}
catch (Exception e)
{
UnityEngine.Debug.LogError(e.Message);
}
}
}
}
}
private void ProcessChildEval(JsEnv jsEnv)
{
if (eval.Count > 0)
{
List<(string, string)> chunks = new List<(string, string)>();
lock (eval)
{
int count = PROCESS_COUNT;
while (count-- > 0 && eval.Count > 0)
chunks.Add(eval.Dequeue());
}
for (int i = 0; i < chunks.Count; i++)
{
try
{
var chunk = chunks[i];
jsEnv.Eval(chunk.Item1, chunk.Item2);
}
catch (Exception e)
{
UnityEngine.Debug.LogError(e.Message);
}
}
}
}
private void ProcessAsyncing()
{
if (Thread.CurrentThread.ManagedThreadId == MAIN_THREAD_ID)
{
sync.ProcessMain();
loader.ProcessMain();
}
else
sync.ProcessChild();
}
/// <summary>
/// 获取同步锁定, 返回是否成功
/// (注:如果两条线程都锁定则会死锁(它们都在等待对方同步), 因此只能有一条线程锁定同步状态)
/// </summary>
internal bool AcquireSyncing()
{
var timeout = DateTime.Now + TimeSpan.FromMilliseconds(lockTimeout);
//如果未处于同步中直接返回, 否则同步后等待状态更新
while (DateTime.Now <= timeout)
{
//请求锁
locker.AcquireWriterLock(lockTimeout);
if (!this.syncing)
{
this.syncing = true;
locker.ReleaseWriterLock();
return true;
}
ProcessAsyncing();
//释放锁
locker.ReleaseWriterLock();
Thread.Sleep(20);
}
return false;
}
/// <summary> 释放同步锁定 </summary>
internal void ReleaseSyncing()
{
locker.AcquireWriterLock(lockTimeout);
this.syncing = false;
locker.ReleaseWriterLock();
}
private class Event
{
public string name;
public Package data;
}
private class SyncLoader : ILoader
{
private JsWorker worker = null;
//脚本缓存
private Dictionary<string, string> scripts;
private Dictionary<string, string> debugPaths;
private Dictionary<string, bool> state;
//这个ILoader只能在主线程调用, 而本实例化对象在子线程中使用需要通过主线程同步
private ILoader loader;
//线程安全
private ReaderWriterLock locker = new ReaderWriterLock();
private const int lockTimeout = 1000;
//加载内容
private string filePath = null;
private bool fileExists = false;
private string readPath = null;
private string readContent = null;
private string debugpath = null;
public SyncLoader(JsWorker worker, ILoader loader)
{
this.worker = worker;
this.loader = loader;
this.scripts = new Dictionary<string, string>();
this.debugPaths = new Dictionary<string, string>();
this.state = new Dictionary<string, bool>();
}
public bool FileExists(string filepath)
{
bool result = false;
if (this.state.TryGetValue(filepath, out result))
return result;
//获取同步状态
if (!worker.AcquireSyncing())
throw new Exception("Other thread is syncing!");
//写入主线程
locker.AcquireWriterLock(lockTimeout);
this.filePath = filepath;
this.fileExists = false;
locker.ReleaseWriterLock();
//等待主线程同步
try
{
while (true)
{
locker.AcquireReaderLock(lockTimeout);
if (this.filePath == null)
break;
locker.ReleaseReaderLock();
}
this.state.Add(filepath, this.fileExists);
return this.fileExists;
}
finally
{
locker.ReleaseReaderLock();
worker.ReleaseSyncing();
}
}
public string ReadFile(string filepath, out string debugpath)
{
string script = null;
if (this.scripts.TryGetValue(filepath, out script))
{
debugpath = this.debugPaths[filepath];
return script;
}
//获取同步状态
if (!worker.AcquireSyncing())
throw new Exception("Other thread is syncing!");
//写入主线程
locker.AcquireWriterLock(lockTimeout);
this.readPath = filepath;
this.readContent = null;
this.debugpath = null;
locker.ReleaseWriterLock();
//等待主线程同步
try
{
while (true)
{
locker.AcquireReaderLock(lockTimeout);
if (this.readPath == null)
break;
locker.ReleaseReaderLock();
}
this.scripts.Add(filepath, this.readContent);
this.debugPaths.Add(filepath, this.debugpath);
debugpath = this.debugpath;
return this.readContent;
}
finally
{
locker.ReleaseReaderLock();
worker.ReleaseSyncing();
}
}
public void ProcessMain()
{
if (this.filePath != null || this.readPath != null)
{
try
{
locker.AcquireWriterLock(lockTimeout);
if (this.filePath != null)
{
this.fileExists = loader.FileExists(this.filePath);
this.filePath = null;
}
if (this.readPath != null)
{
this.readContent = loader.ReadFile(this.readPath, out this.debugpath);
this.readPath = null;
}
}
catch (Exception e)
{
this.filePath = null;
this.fileExists = false;
this.readPath = null;
this.readContent = null;
this.debugpath = null;
throw e;
}
finally
{
locker.ReleaseWriterLock();
}
}
}
}
public class SyncProcess
{
private JsWorker worker = null;
//线程安全
private ReaderWriterLock locker = new ReaderWriterLock();
private const int lockTimeout = 1000;
//同步消息
private string m_eventName = null;
private Package m_eventData = null;
private string c_eventName = null;
private Package c_eventData = null;
public SyncProcess(JsWorker worker)
{
this.worker = worker;
}
public object CallMain(string name, Package data, bool throwOnError = true)
{
if (name == null) return null;
//获取同步状态
if (!worker.AcquireSyncing())
{
if (!throwOnError) return null;
throw new Exception("Other thread is syncing!");
}
//写入主线程
locker.AcquireWriterLock(lockTimeout);
this.m_eventName = name;
this.m_eventData = data;
locker.ReleaseWriterLock();
//等待主线程同步
try
{
while (true)
{
locker.AcquireReaderLock(lockTimeout);
if (this.m_eventName == null)
break;
locker.ReleaseReaderLock();
}
return this.m_eventData;
}
finally
{
locker.ReleaseReaderLock();
worker.ReleaseSyncing();
}
}
public object CallChild(string name, Package data, bool throwOnError = true)
{
if (name == null) return null;
//获取同步状态
if (!worker.AcquireSyncing())
{
if (!throwOnError) return null;
throw new Exception("Other thread is syncing!");
}
//写入子线程
locker.AcquireWriterLock(lockTimeout);
this.c_eventName = name;
this.c_eventData = data;
locker.ReleaseWriterLock();
//等待子线程同步
try
{
while (true)
{
locker.AcquireReaderLock(lockTimeout);
if (this.c_eventName == null)
break;
locker.ReleaseReaderLock();
}
return this.c_eventData;
}
finally
{
locker.ReleaseReaderLock();
worker.ReleaseSyncing();
}
}
public void ProcessMain()
{
if (this.m_eventName != null)
{
Func<string, Package, Package> func = this.worker.messageByMain;
try
{
locker.AcquireWriterLock(lockTimeout);
Package data = null;
if (this.m_eventName != null && func != null)
data = func(this.m_eventName, this.m_eventData);
this.m_eventData = data;
}
catch (Exception e)
{
this.m_eventData = null;
throw e;
}
finally
{
this.m_eventName = null;
locker.ReleaseWriterLock();
}
}
}
public void ProcessChild()
{
if (this.c_eventName != null)
{
Func<string, Package, Package> func = this.worker.messageByChild;
try
{
locker.AcquireWriterLock(lockTimeout);
Package data = null;
if (this.c_eventName != null && func != null)
data = func(this.c_eventName, this.c_eventData);
this.c_eventData = data;
}
catch (Exception e)
{
this.c_eventData = null;
throw e;
}
finally
{
this.c_eventName = null;
locker.ReleaseWriterLock();
}
}
}
}
public class Package
{
/**data type */
public Type type;
/**data value */
public object value;
/**info */
public object info;
/**object id */
public int id = -1;
public static byte[] ToBytes(Puerts.ArrayBuffer value)
{
if (value != null)
{
var source = value.Bytes;
var result = new byte[source.Length];
Array.Copy(source, 0, result, 0, source.Length);
return result;
}
return null;
}
public static Puerts.ArrayBuffer ToArrayBuffer(byte[] value)
{
if (value != null)
return new Puerts.ArrayBuffer(value);
return null;
}
}
public enum Type
{
Unknown,
Value,
Object,
Array,
Function,
/**ArrayBuffer类型为指针传递, 直接传递将多线程共享内存而crash */
ArrayBuffer,
RefObject
}
}