网络接口调整

This commit is contained in:
宝爷
2019-10-07 16:29:45 +08:00
parent 2eca2543fc
commit 181e6237a0
15 changed files with 868 additions and 6 deletions

73
.gitignore vendored
View File

@@ -1,6 +1,67 @@
/local
/temp
/library
/.vscode
/packages
.DS_Store
#/////////////////////////////////////////////////////////////////////////////
# Cocos Creator Projects
#/////////////////////////////////////////////////////////////////////////////
build/
library/
temp/
local/
jsb-default/
jsb-binary/
jsb-link/
web-desktop/
# web-mobile/ 发布网页版本使用
runtime/
build/
jsb/
export/
#/////////////////////////////////////////////////////////////////////////////
# Logs and databases
#/////////////////////////////////////////////////////////////////////////////
*.log
*.sql
*.sqlite
#/////////////////////////////////////////////////////////////////////////////
# files for debugger
#/////////////////////////////////////////////////////////////////////////////
*.sln
*.csproj
*.pidb
*.unityproj
*.suo
#/////////////////////////////////////////////////////////////////////////////
# OS generated files
#/////////////////////////////////////////////////////////////////////////////
.DS_Store
ehthumbs.db
Thumbs.db
#/////////////////////////////////////////////////////////////////////////////
# exvim files
#/////////////////////////////////////////////////////////////////////////////
*UnityVS.meta
*.err
*.err.meta
*.exvim
*.exvim.meta
*.vimentry
*.vimentry.meta
*.vimproject
*.vimproject.meta
.vimfiles.*/
.exvim.*/
quick_gen_project_*_autogen.bat
quick_gen_project_*_autogen.bat.meta
quick_gen_project_*_autogen.sh
quick_gen_project_*_autogen.sh.meta
.exvim.app
.idea/
.vscode/

View File

@@ -0,0 +1,7 @@
{
"ver": "1.0.1",
"uuid": "b8f13390-ade4-44a3-8658-6ebf77febb27",
"isSubpackage": false,
"subpackageName": "",
"subMetas": {}
}

View File

@@ -0,0 +1,109 @@
/*
* 事件管理器,事件的监听、触发、移除
*
* 2018-9-20 by 宝爷
*/
export type EventManagerCallFunc = (eventName: string, eventData: any) => void;
interface CallBackTarget {
callBack: EventManagerCallFunc,
target: any,
}
export class EventManager {
private static instance: EventManager = null;
public static getInstance(): EventManager {
if (!this.instance) {
this.instance = new EventManager();
}
return this.instance;
}
public static destroy(): void {
if (this.instance) {
this.instance = null;
}
}
private constructor() {
}
private _eventListeners: { [key: string]: CallBackTarget[] } = {};
private getEventListenersIndex(eventName: string, callBack: EventManagerCallFunc, target?: any): number {
let index = -1;
for (let i = 0; i < this._eventListeners[eventName].length; i++) {
let iterator = this._eventListeners[eventName][i];
if (iterator.callBack == callBack && (!target || iterator.target == target)) {
index = i;
break;
}
}
return index;
}
addEventListener(eventName: string, callBack: EventManagerCallFunc, target?: any): boolean {
if (!eventName) {
cc.warn("eventName is empty" + eventName);
return;
}
if (null == callBack) {
cc.log('addEventListener callBack is nil');
return false;
}
let callTarget: CallBackTarget = { callBack: callBack, target: target };
if (null == this._eventListeners[eventName]) {
this._eventListeners[eventName] = [callTarget];
} else {
let index = this.getEventListenersIndex(eventName, callBack, target);
if (-1 == index) {
this._eventListeners[eventName].push(callTarget);
}
}
return true;
}
setEventListener(eventName: string, callBack: EventManagerCallFunc, target?: any): boolean {
if (!eventName) {
cc.warn("eventName is empty" + eventName);
return;
}
if (null == callBack) {
cc.log('setEventListener callBack is nil');
return false;
}
let callTarget: CallBackTarget = { callBack: callBack, target: target };
this._eventListeners[eventName] = [callTarget];
return true;
}
removeEventListener(eventName: string, callBack: EventManagerCallFunc, target?: any) {
if (null != this._eventListeners[eventName]) {
let index = this.getEventListenersIndex(eventName, callBack, target);
if (-1 != index) {
this._eventListeners[eventName].splice(index, 1);
}
}
}
raiseEvent(eventName: string, eventData?: any) {
console.log(`==================== raiseEvent ${eventName} begin | ${JSON.stringify(eventData)}`);
if (null != this._eventListeners[eventName]) {
// 将所有回调提取出来,再调用,避免调用回调的时候操作了事件的删除
let callbackList: CallBackTarget[] = [];
for (const iterator of this._eventListeners[eventName]) {
callbackList.push({ callBack: iterator.callBack, target: iterator.target });
}
for (const iterator of callbackList) {
iterator.callBack.call(iterator.target, eventName, eventData);
}
}
console.log(`==================== raiseEvent ${eventName} end`);
}
}
export let EventMgr = EventManager.getInstance();

View File

@@ -0,0 +1,9 @@
{
"ver": "1.0.5",
"uuid": "d23b8dc4-fd4c-43e4-87db-4c5bc3bfa818",
"isPlugin": false,
"loadPluginInWeb": true,
"loadPluginInNative": true,
"loadPluginInEditor": false,
"subMetas": {}
}

View File

@@ -0,0 +1,166 @@
/*
* 任务队列管理器
* 1. 传入任务,进入队列顺序执行
* 2. 支持优先级从小到大排序priority越小优先级越高
* 3. 支持队列Tag允许多个互不影响的队列执行
* 4. 支持清理任务队列
* 5. 一个任务只能完成一次,避免代码的原因多次调用完成,导致后续任务提前执行
*
* 6. 调试模式下记录了每个Task添加时的堆栈方便调试可以快速查看哪个任务没有结束
*
* 2018-5-7 by 宝爷
*/
// 任务结束回调
export type TaskFinishCallback = () => void;
// 任务执行回调
export type TaskCallback = (TaskFinishCallback) => void;
class TaskInfo {
public task: TaskCallback;
public priority: number;
public constructor(task: TaskCallback, priority: number) {
this.task = task;
this.priority = priority;
}
}
export class TaskQueue {
private _curTask: TaskInfo = null;
private _taskQueue: TaskInfo[] = Array<TaskInfo>();
// 添加一个任务,如果当前没有任务在执行,该任务会立即执行,否则进入队列等待
public pushTask(task: TaskCallback, priority: number = 0): void {
let taskInfo = new TaskInfo(task, priority);
if (this._taskQueue.length > 0) {
for (var i: number = this._taskQueue.length - 1; i >= 0; --i) {
if (this._taskQueue[i].priority <= priority) {
this._taskQueue.splice(i + 1, 0, taskInfo);
return;
}
}
}
// 插到头部
this._taskQueue.splice(0, 0, taskInfo);
if (this._curTask == null) {
this.executeNextTask();
}
}
public clearTask(): void {
this._curTask = null;
this._taskQueue.length = 0;
}
private executeNextTask(): void {
let taskInfo = this._taskQueue.shift() || null;
this._curTask = taskInfo;
if (taskInfo) {
taskInfo.task(() => {
if (taskInfo === this._curTask) {
this.executeNextTask();
} else {
console.warn("your task finish twice!");
}
});
}
}
}
export class TaskManager {
private static _instance: TaskManager = null;
private _taskQueues: { [key: number]: TaskQueue } = {}
public static getInstance(): TaskManager {
if (!this._instance) {
this._instance = new TaskManager();
}
return this._instance;
}
public static destory(): void {
this._instance = null;
}
private constructor() {
}
public pushTask(task: TaskCallback, priority: number = 0): void {
return this.getTaskQueue().pushTask(task, priority);
}
public pushTaskByTag(task: TaskCallback, tag: number, priority: number = 0): void {
return this.getTaskQueue(tag).pushTask(task, priority);
}
public clearTaskQueue(tag: number = 0): void {
let taskQueue = this._taskQueues[tag];
if (taskQueue) {
taskQueue.clearTask();
}
}
public clearAllTaskQueue(): void {
for (let queue in this._taskQueues) {
this._taskQueues[queue].clearTask();
}
this._taskQueues = {}
}
private getTaskQueue(tag: number = 0): TaskQueue {
let taskQueue = this._taskQueues[tag];
if (taskQueue == null) {
taskQueue = new TaskQueue();
this._taskQueues[tag] = taskQueue;
}
return taskQueue;
}
}
/* 测试用例:
* 1. 测试多个任务的执行顺序 + 优先级
* 2. 测试在执行任务的过程中动态添加新任务
export function testQueue() {
let creatTask = (idx, pri): TaskCallback => {
return (finish) => {
console.log(`execute task ${idx} priority ${pri}`);
finish();
};
};
let tag = 0;
let begin = (finish) => {
for (var i = 0; i < 100; ++i) {
let priority = 0;
if (i % 10 == 0) {
priority = -1;
} else if (i == 88) {
priority = 1;
} else if (i == 22) {
let task = creatTask(1.1, priority);
TaskManager.getInstance().pushTaskByTag(task, tag, priority);
task = creatTask(1.2, priority);
TaskManager.getInstance().pushTaskByTag(task, tag, priority);
task = creatTask(1.3, priority);
TaskManager.getInstance().pushTaskByTag(task, tag, priority);
} else if (i == 51 && tag == 2) {
// 清理之后,添加的任务会立即执行...
TaskManager.getInstance().clearTaskQueue(tag);
}
let task = creatTask(i, priority);
Object.defineProperty(task, "idx", { value: i });
TaskManager.getInstance().pushTaskByTag(task, tag, priority);
}
console.log("add task finish, start test");
finish();
// 测试重复调用结束
finish();
// tag为2时的finish两次都会报警告因为begin已经被清理了
}
TaskManager.getInstance().pushTaskByTag(begin, tag);
tag = 2;
TaskManager.getInstance().pushTaskByTag(begin, tag);
}*/

View File

@@ -0,0 +1,9 @@
{
"ver": "1.0.5",
"uuid": "4ed3ba86-aa0d-4b93-864b-adc0f8d58177",
"isPlugin": false,
"loadPluginInWeb": true,
"loadPluginInNative": true,
"loadPluginInEditor": false,
"subMetas": {}
}

View File

@@ -0,0 +1,7 @@
{
"ver": "1.0.1",
"uuid": "7f2dddd0-7938-4352-802a-fa3d6aac09ec",
"isSubpackage": false,
"subpackageName": "",
"subMetas": {}
}

View File

@@ -0,0 +1,41 @@
export type NetData = (string | ArrayBufferLike | Blob | ArrayBufferView);
export type NetCallFunc = (mainCmd: number, subCmd: number, data: any) => void;
// 回调对象
export interface CallbackObject {
target: any, // 回调对象不为null时调用target.callback(xxx)
callback: NetCallFunc, // 回调函数
}
// 请求对象
export interface RequestObject {
buffer: NetData, // 请求的Buffer
rspCmd: number, // 等待响应指令
rspObject: CallbackObject, // 等待响应的回调对象
}
// 协议对象
export interface IProtocolHelper {
getHeadlen(): number; // 返回包头长度
getHearbeat(): NetData; // 返回一个心跳包
checkHead(msg: NetData): boolean; // 检查数据头部是否合法
checkCmd(msg: NetData): number; // 获取协议类型或id
}
// Socket对象
export interface ISocket {
onConnected: (event) => void;
onMessage: (msg: NetData) => void;
onError: (event) => void;
onClosed: (event) => void;
connect(options: any) : boolean;
send(buffer: NetData) : boolean;
close(code?: number, reason?: string);
}
// 网络提示对象
export interface INetworkTips {
reconnectTips(isShow : boolean): void;
requestTips(isShow : boolean): void;
}

View File

@@ -0,0 +1,9 @@
{
"ver": "1.0.5",
"uuid": "92a57531-69f5-4125-b17e-4fb69dedf652",
"isPlugin": false,
"loadPluginInWeb": true,
"loadPluginInNative": true,
"loadPluginInEditor": false,
"subMetas": {}
}

View File

@@ -0,0 +1,9 @@
/*
* 网络节点管理类
*
*/
export class NetManager {
}

View File

@@ -0,0 +1,9 @@
{
"ver": "1.0.5",
"uuid": "a83ab7c0-5761-4aac-b367-a108f45b61ad",
"isPlugin": false,
"loadPluginInWeb": true,
"loadPluginInNative": true,
"loadPluginInEditor": false,
"subMetas": {}
}

View File

@@ -0,0 +1,347 @@
import { ISocket, INetworkTips, IProtocolHelper, RequestObject, CallbackObject, NetData, NetCallFunc } from "./NetInterface";
/*
* CocosCreator网络节点基类以及网络相关接口定义
* 1. 网络连接、断开、请求发送、数据接收等基础功能
* 2. 心跳机制
* 3. 断线重连 + 请求重发
* 4. 调用网络屏蔽层
*
* 2018-5-7 by 宝爷
*/
type ExecuterFunc = (callback: CallbackObject, buffer: NetData) => void;
type VoidFunc = () => void;
type BoolFunc = () => boolean;
export enum NetNodeState {
Closed, // 已关闭
Connecting, // 连接中
Checking, // 验证中
Working, // 可传输数据
}
export enum NetNodeConnectType {
Normal,
ReConnect,
}
export class NetNode {
protected _isAutoReconnect: boolean = false; // 是否在网络断开之后自动重连
protected _isSocketInit: boolean = false; // Socket是否初始化过
protected _isSocketOpen: boolean = false; // Socket是否连接成功过
protected _connType: NetNodeConnectType = NetNodeConnectType.Normal; // 连接类型
protected _state: NetNodeState = NetNodeState.Closed; // 节点当前状态
protected _socket: ISocket = null; // Socket对象可能是原生socket、websocket、wx.socket...)
protected _host: string; // IP
protected _port: number; // 端口
protected _networkTips: INetworkTips = null; // 网络提示ui对象请求提示、断线重连提示等
protected _protocolHelper: IProtocolHelper = null; // 包解析对象
protected _connectedCallback: VoidFunc = null; // 连接完成回调
protected _disconnectCallback: BoolFunc = null; // 断线回调
protected _callbackExecuter: ExecuterFunc = null; // 回调执行
protected _keepAliveTimer: any = null; // 心跳定时器
protected _receiveMsgTimer: any = null; // 接收数据定时器
protected _reconnectTimer: any = null; // 重连定时器
protected _heartTime: number = 10000; // 心跳间隔
protected _receiveTime: number = 6000000; // 多久没收到数据断开
protected _reconnetTimeOut: number = 8000000; // 重连间隔
protected _requests: RequestObject[] = Array<RequestObject>(); // 请求列表
protected _listener: { [key: number]: CallbackObject[] } = {} // 监听者列表
/********************** 网络相关处理 *********************/
public init(socket: ISocket, networkTips: any = null) {
console.log(`NetNode init socket`);
this._socket = socket;
this._networkTips = networkTips;
}
public connect(host: string, port: number, auotReconnect: boolean = true, connType: NetNodeConnectType = NetNodeConnectType.Normal) {
if (this._socket && this._state == NetNodeState.Closed) {
if (!this._isSocketInit) {
this.initSocket();
}
this._state = NetNodeState.Connecting;
this._isAutoReconnect = auotReconnect;
this._socket.connect({ip : host, port});
this._connType = connType;
if (connType == NetNodeConnectType.Normal) {
this._host = host;
this._port = port;
}
console.log(`NetNode connect to ${host}:${port}`);
} else {
console.error(`NetNode connect error! should init socket! state ${this._state}`);
}
}
protected initSocket() {
this._socket.onConnected = (event) => { this.onConnected(event) };
this._socket.onMessage = (msg) => { this.onMessage(msg) };
this._socket.onError = (event) => { this.onError(event) };
this._socket.onClosed = (event) => { this.onClosed(event) };
this._isSocketInit = true;
}
// 网络连接成功
protected onConnected(event) {
console.log("NetNode onConnected!")
this._isSocketOpen = true;
// 如果设置了
if (this._connectedCallback !== null) {
this._state = NetNodeState.Checking;
this._connectedCallback();
} else {
this.onChecked();
}
console.log("NetNode onConnected! state ="+this._state);
}
// 连接验证成功,进入工作状态
protected onChecked() {
console.log("NetNode onChecked!")
this._state = NetNodeState.Working;
// 关闭重连中的状态显示
if (this._networkTips !== null) {
this._networkTips.reconnectTips(false);
}
// 重发待发送信息
console.log(`NetNode flush ${this._requests.length} request`)
if (this._requests.length > 0) {
for (var i = 0; i < this._requests.length;) {
let req = this._requests[i];
this._socket.send(req.buffer);
if (req.rspObject == null || req.rspCmd <= 0) {
this._requests.splice(i, 1);
} else {
++i;
}
}
// 如果还有等待返回的请求,启动网络请求层
if (this._networkTips != null) {
if (this._requests.length > 0) {
console.log(`NetNode startRequestTips`)
this._networkTips.requestTips(true);
} else {
this._networkTips.requestTips(false);
}
}
}
}
// 接收到一个完整的消息包
protected onMessage(msg): void {
// console.log(`NetNode onMessage status = ` + this._state);
// 进行头部的校验(实际包长与头部长度是否匹配)
if (!this._protocolHelper.checkHead(msg)) {
console.error(`NetNode checkHead Error`);
return;
}
// 接受到数据,重新定时收数据计时器
this.resetReceiveMsgTimer();
// 重置心跳包发送器
this.resetHearbeatTimer();
// 触发消息执行
let rspCmd = this._protocolHelper.checkCmd(msg);
console.log(`NetNode onMessage rspCmd = ` + rspCmd);
// 优先触发request队列
if (this._requests.length > 0) {
for (let reqIdx in this._requests) {
let req = this._requests[reqIdx];
if (req.rspCmd == rspCmd) {
console.log(`NetNode execute request rspcmd ${rspCmd}`);
this._callbackExecuter(req.rspObject, msg);
this._requests.splice(parseInt(reqIdx), 1);
break;
}
}
console.log(`NetNode still has ${this._requests.length} request watting`);
if (this._requests.length == 0 && this._networkTips) {
this._networkTips.requestTips(false);
}
}
let listeners = this._listener[rspCmd];
if (null != listeners) {
for (const rsp of listeners) {
console.log(`NetNode execute listener cmd ${rspCmd}`);
this._callbackExecuter(rsp, msg);
}
}
}
protected onError(event) {
console.error(event);
}
protected onClosed(event) {
this.clearTimer();
// 执行断线回调
if (this._disconnectCallback && !this._disconnectCallback()) {
console.log(`disconnect return!`)
return;
}
// 自动重连
if (this._isAutoReconnect) {
if (this._networkTips) {
this._networkTips.reconnectTips(true);
}
this._reconnectTimer = setTimeout(() => {
this._socket.close();
this._state = NetNodeState.Closed;
this.connect(this._host, this._port, this._isAutoReconnect, NetNodeConnectType.ReConnect);
}, this._reconnetTimeOut);
} else {
this._state = NetNodeState.Closed;
}
}
public close(code?: number, reason?: string) {
this.clearTimer();
this._listener = {};
this._requests.length = 0;
if (this._networkTips) {
this._networkTips.reconnectTips(false);
this._networkTips.requestTips(false);
}
if (this._socket) {
this._socket.close(code, reason);
} else {
this._state = NetNodeState.Closed;
}
}
// 只是关闭Socket套接字仍然重用缓存与当前状态
public closeSocket(code?: number, reason?: string) {
if (this._socket) {
this._socket.close(code, reason);
}
}
// 发起请求,如果当前处于重连中,进入缓存列表等待重连完成后发送
public send(buf: NetData, force: boolean = false) {
if (this._state == NetNodeState.Working || force) {
console.log(`socket send ...`);
this._socket.send(buf);
} else if (this._state == NetNodeState.Checking ||
this._state == NetNodeState.Connecting) {
this._requests.push({
buffer: buf,
rspCmd: 0,
rspObject: null
});
console.log("NetNode socket is busy, push to send buffer, current state is " + this._state);
} else {
console.error("NetNode request error! current state is " + this._state);
}
}
// 发起请求,并进入缓存列表,
public sendWithTimeout(buf: NetData, rspCmd: number, rspObject: CallbackObject, showTips: boolean = true, force: boolean = false) {
if (this._state == NetNodeState.Working || force) {
this._socket.send(buf);
}
console.log(`NetNode request with timeout for ${rspCmd}`);
// 进入发送缓存列表
this._requests.push({
buffer: buf, rspCmd, rspObject
});
// 启动网络请求层
if (this._networkTips !== null && showTips) {
this._networkTips.requestTips(true);
}
}
/********************** 回调相关处理 *********************/
public setResponeHandler(cmd: number, callback: NetCallFunc, target?: any): boolean {
if (callback == null) {
console.error(`NetNode setResponeHandler error ${cmd}`);
return false;
}
this._listener[cmd] = [{ target, callback }];
return true;
}
public addResponeHandler(cmd: number, callback: NetCallFunc, target?: any): boolean {
if (callback == null) {
console.error(`NetNode addResponeHandler error ${cmd}`);
return false;
}
let rspObject = { target, callback };
if (null == this._listener[cmd]) {
this._listener[cmd] = [rspObject];
} else {
let index = this.getNetListenersIndex(cmd, rspObject);
if (-1 == index) {
this._listener[cmd].push(rspObject);
}
}
return true;
}
public removeResponeHandler(cmd: number, callback: NetCallFunc, target?: any) {
if (null != this._listener[cmd] && callback != null) {
let index = this.getNetListenersIndex(cmd, { target, callback });
if (-1 != index) {
this._listener[cmd].splice(index, 1);
}
}
}
protected getNetListenersIndex(cmd: number, rspObject: CallbackObject): number {
let index = -1;
for (let i = 0; i < this._listener[cmd].length; i++) {
let iterator = this._listener[cmd][i];
if (iterator.callback == rspObject.callback
&& iterator.target == rspObject.target) {
index = i;
break;
}
}
return index;
}
/********************** 心跳、超时相关处理 *********************/
protected resetReceiveMsgTimer() {
if (this._receiveMsgTimer !== null) {
clearTimeout(this._receiveMsgTimer);
}
this._receiveMsgTimer = setTimeout(() => {
console.warn("NetNode recvieMsgTimer close socket!");
this._socket.close();
}, this._receiveTime);
}
protected resetHearbeatTimer() {
if (this._keepAliveTimer !== null) {
clearTimeout(this._keepAliveTimer);
}
this._keepAliveTimer = setTimeout(() => {
console.log("NetNode keepAliveTimer send Hearbeat")
this.send(this._protocolHelper.getHearbeat());
}, this._heartTime);
}
protected clearTimer() {
if (this._receiveMsgTimer !== null) {
clearTimeout(this._receiveMsgTimer);
}
if (this._keepAliveTimer !== null) {
clearTimeout(this._keepAliveTimer);
}
if (this._reconnectTimer !== null) {
clearTimeout(this._reconnectTimer);
}
}
public rejectReConnect(){
this._isAutoReconnect = false;
this.clearTimer();
}
}

View File

@@ -0,0 +1,9 @@
{
"ver": "1.0.5",
"uuid": "75b71ada-9878-434a-96fb-c03cfb4491c9",
"isPlugin": false,
"loadPluginInWeb": true,
"loadPluginInNative": true,
"loadPluginInEditor": false,
"subMetas": {}
}

View File

@@ -0,0 +1,61 @@
import { ISocket, NetData } from "./NetInterface";
/*
* WebSocket封装
* 1. 连接/断开相关接口
* 2. 网络异常回调
* 3. 数据发送与接收
*
* 2018-5-14 by 宝爷
*/
export class WebSock implements ISocket {
private _ws: WebSocket = null; // websocket对象
onConnected: (event) => void = null;
onMessage: (msg) => void = null;
onError: (event) => void = null;
onClosed: (event) => void = null;
connect(options: any) {
if (this._ws) {
if (this._ws.readyState === WebSocket.CONNECTING) {
console.log("websocket connecting, wait for a moment...")
return false;
}
}
let url = null;
if(options.url) {
url = options.url;
} else {
let ip = options.ip;
let port = options.port;
let protocol = options.protocol;
url = `${protocol}://${ip}:${port}`;
}
this._ws = new WebSocket(url);
this._ws.binaryType = options.binaryType ? options.binaryType : "arraybuffer";
this._ws.onmessage = (event) => {
this.onMessage(event.data);
};
this._ws.onopen = this.onConnected;
this._ws.onerror = this.onError;
this._ws.onclose = this.onClosed;
return true;
}
send(buffer: NetData) {
if (this._ws.readyState == WebSocket.OPEN)
{
this._ws.send(buffer);
return true;
}
return false;
}
close(code?: number, reason?: string) {
this._ws.close();
}
}

View File

@@ -0,0 +1,9 @@
{
"ver": "1.0.5",
"uuid": "7c5d5446-f565-45c1-9e5d-e9c46c91f3a3",
"isPlugin": false,
"loadPluginInWeb": true,
"loadPluginInNative": true,
"loadPluginInEditor": false,
"subMetas": {}
}