diff --git a/cocos-creator/3X/CocosCreator3Dumper.ts b/cocos-creator/3X/CocosCreator3Dumper.ts new file mode 100644 index 0000000..40217a1 --- /dev/null +++ b/cocos-creator/3X/CocosCreator3Dumper.ts @@ -0,0 +1,13 @@ +import AbstractDumper from "./sdk/AbstractDumper" +import CCNode from "./CocosCreator3Node" +import { director, Node, View } from "cc" + +export default class Dumper extends AbstractDumper { + getRoot() { + const scene = director.getScene()! as unknown as Node + const size = View.instance.getVisibleSize() + CCNode.screenHeight = size.height + CCNode.screenWidth = size.width + return new CCNode(scene) + } +} diff --git a/cocos-creator/3X/CocosCreator3Node.ts b/cocos-creator/3X/CocosCreator3Node.ts new file mode 100644 index 0000000..ff82a03 --- /dev/null +++ b/cocos-creator/3X/CocosCreator3Node.ts @@ -0,0 +1,116 @@ +import { Node, UITransformComponent } from "cc" +import AbstractNode from "./sdk/AbstractNode" + +export default class CCCNode implements AbstractNode { + node: Node + static screenWidth: number = 0 + static screenHeight: number = 0 + constructor(node: Node) { + this.node = node + } + + getParent() { + let parent = this.node.parent + if (!parent) { + return null + } + return new CCCNode(parent) + } + + getChildren() { + let children: CCCNode[] = [] + let nodeChildren = this.node.children + for (const i of nodeChildren) { + children.push(new CCCNode(i)) + } + return children + } + + getAttr(attrName: string) { + if (attrName === "visible") { + return this.node.activeInHierarchy + } else if (attrName === "name") { + return this.node.name || "" + } else if (attrName === "text") { + for (const component of this.node.components) { + if ("string" in component) { + return (component as any).string + } + } + return "" + } else if (attrName === "type") { + const componentSize = this.node.components.length + let ntype: string = "" + //一般第一个是UI组件,pass + switch (componentSize) { + case 0: + return "" + case 1: + ntype = this.node.components[0].name + break + default: + ntype = this.node.components[1].name + break + } + return ntype.replace(/\w+\./, "") + } else if (attrName === "pos") { + // 转换成归一化坐标系,原点左上角 + let pos = this.node.worldPosition + return [pos.x / CCCNode.screenWidth, 1 - pos.y / CCCNode.screenHeight] + } else if (attrName === "size") { + // 转换成归一化坐标系 + let size = this.node.getComponent(UITransformComponent)?.contentSize + if (!size) return [0, 0] + return [ + size.width / CCCNode.screenWidth, + size.height / CCCNode.screenHeight, + ] + } else if (attrName === "scale") { + return [this.node.scale.x, this.node.scale.y] + } else if (attrName === "anchorPoint") { + let ui = this.node.getComponent(UITransformComponent) + if (!ui) return [0, 0] + return [ui.anchorX, ui.anchorY] + } else if (attrName == "touchable") { + return true + } else if (attrName === "tag") { + return "" + } else if (attrName === "enabled") { + return true + } else if (attrName === "rotation") { + return this.node.rotation + } + + return undefined + } + + getAvailableAttributeNames() { + return [ + "name", + "type", + "visible", + "pos", + "size", + "scale", + "anchorPoint", + "text", + "enabled", + "rotation", + ] + } + + setAttr() {} + + enumerateAttrs() { + var ret: any = {} + var allAttrNames = this.getAvailableAttributeNames() + for (var i in allAttrNames) { + var attrName = allAttrNames[i] + var attrVal = this.getAttr(attrName) + if (attrVal !== undefined) { + ret[attrName] = attrVal + } + } + return ret + } +} diff --git a/cocos-creator/3X/Poco.ts b/cocos-creator/3X/Poco.ts new file mode 100644 index 0000000..1abb92e --- /dev/null +++ b/cocos-creator/3X/Poco.ts @@ -0,0 +1,108 @@ +//@ts-nocheck +import Dumper from "./CocosCreator3Dumper" +const POCO_SDK_VERSION = "1.1.0" + +type retInfo = { + id: string + jsonrpc: string + result: any + error: { message: string } | null +} + +export default class PocoManager { + port: number + poco: Dumper + rpc_dispacher: any + constructor(port: number) { + this.port = port || 5003 + this.poco = new Dumper() + this.rpc_dispacher = { + getSDKVersion: function () { + return POCO_SDK_VERSION + }, + GetSDKVersion: function () { + return POCO_SDK_VERSION + }, // for the compatibility + dump: this.poco.dumpHierarchy, + Dump: this.poco.dumpHierarchy, // for the compatibility + test: function () { + return "test" + }, + } + this.init_server() + } + + handle_request(req: any) { + var ret: retInfo = { + id: req.id, + jsonrpc: req.jsonrpc, + result: null, + error: null, + } + var method = req.method + var func = this.rpc_dispacher[method] + if (!func) { + ret.error = { + message: 'No such rpc method "' + method + '", reqid: ' + req.id, + } + } else { + var params = req.params + try { + var result = func.apply(this.poco, params) + ret.result = result + } catch (error: any) { + ret.error = { message: error.stack } + } + } + console.log(ret) + return ret + } + + init_server() { + console.log("try starting wss..") + var that = this + try { + if (typeof WebSocketServer == "undefined") { + console.error("WebSocketServer is not enabled!") + return + } + + var s = new WebSocketServer() + + s.listen(this.port, (err: any) => { + if (!err) console.log("server booted!") + }) + + s.onconnection = function (conn: any) { + console.log("Network onConnection...") + conn.ondata = function (data: any) { + console.log("Network onMessage...") + console.log(data) + try { + var req = JSON.parse(data) + var res = that.handle_request(req) + var sres = JSON.stringify(res) + + conn.send(sres, (err: any) => {}) + } catch (error: any) { + console.log( + "[Poco] error when handling rpc request. req=" + + data + + "\nerror message: " + + error.stack + ) + } + } + conn.onclose = function () { + console.log("connection gone!") + } + } + + s.onclose = function () { + console.log("server is closed!") + } + } catch (err: any) { + console.log(err.stack + "\n" + err.message) + } + } +} diff --git a/cocos-creator/3X/sdk/AbstractDumper.ts b/cocos-creator/3X/sdk/AbstractDumper.ts new file mode 100644 index 0000000..dfb1b19 --- /dev/null +++ b/cocos-creator/3X/sdk/AbstractDumper.ts @@ -0,0 +1,46 @@ +import type AbstractNode from "./AbstractNode" +type dumpInfo = { + name: string + payload: any + children: dumpInfo[] +} +export default class AbstractDumper { + getRoot(): AbstractNode { + throw new Error("not impl") + } + + dumpHierarchy( + node: AbstractNode|boolean, + onlyVisibleNode: boolean=true + ): dumpInfo | null { + if (!node) { + return null + } + if (node===true){ + node=this.getRoot() + } + + var payload = node.enumerateAttrs() + payload['zOrders']={'local':0,'global':0} + var result: dumpInfo = { + name: payload["name"] || node.getAttr("name"), + payload, + children: [], + } + var nodeChildren = node.getChildren() + for (var i in nodeChildren) { + var child = nodeChildren[i] + if ( + !onlyVisibleNode || + payload["visible"] || + child.getAttr("visible") + ) { + result.children.push( + this.dumpHierarchy(child, onlyVisibleNode)! + ) + } + } + + return result + } +} diff --git a/cocos-creator/3X/sdk/AbstractNode.ts b/cocos-creator/3X/sdk/AbstractNode.ts new file mode 100644 index 0000000..0dc077c --- /dev/null +++ b/cocos-creator/3X/sdk/AbstractNode.ts @@ -0,0 +1,8 @@ +export default interface AbstractNode { + getParent(): AbstractNode | null + getChildren(): AbstractNode[] + getAttr(attrName: string): any + setAttr(): void + getAvailableAttributeNames(): string[] + enumerateAttrs(): { [key: string]: any } +} diff --git a/cocos-creator/3X/sdk/Attributor.ts b/cocos-creator/3X/sdk/Attributor.ts new file mode 100644 index 0000000..d0fc3b8 --- /dev/null +++ b/cocos-creator/3X/sdk/Attributor.ts @@ -0,0 +1,16 @@ +export namespace Attributor{ + function getAttr(node, attrName) { + let node_ = node + if (!node.__isPocoNodeWrapper__) { + node_ = node[0] + } + return node_.getAttr(attrName) + } + function setAttr (node, attrName, attrVal) { + let node_ = node + if (!node.__isPocoNodeWrapper__) { + node_ = node[0] + } + node_.setAttr(attrName, attrVal) + } +} \ No newline at end of file diff --git a/cocos-creator/3X/sdk/DefaultMatcher.ts b/cocos-creator/3X/sdk/DefaultMatcher.ts new file mode 100644 index 0000000..f2645e8 --- /dev/null +++ b/cocos-creator/3X/sdk/DefaultMatcher.ts @@ -0,0 +1,61 @@ +interface IComparator { + compare(cond: any, node: any): boolean +} + +class EqualizationComparator implements IComparator { + compare(cond: any, node: any): boolean { + return cond === node + } +} + +class RegexpComparator implements IComparator { + compare(origin: any, pattern: any): boolean { + if (!origin || !pattern) { + return false + } + return origin.toString().match(pattern) !== null + } +} + +export default class DefaultMatcher { + comparators: { [key: string]: IComparator } = { + "attr=": new EqualizationComparator(), + "attr.*=": new RegexpComparator(), + } + match(cond: any, node: any): boolean { + var op = cond[0] + var args = cond[1] + + // 条件匹配 + if (op === "and") { + for (var i in args) { + var arg = args[i] + if (!this.match(arg, node)) { + return false + } + } + return true + } + + if (op === "or") { + for (var i in args) { + var arg = args[i] + if (this.match(arg, node)) { + return true + } + } + return false + } + + // 属性匹配 + var comparator: IComparator = this.comparators[op] + if (comparator) { + var attribute = args[0] + var value = args[1] + var targetValue = node.getAttr(attribute) + return comparator.compare(targetValue, value) + } + + return false + } +} diff --git a/cocos-creator/3X/sdk/Selector.ts b/cocos-creator/3X/sdk/Selector.ts new file mode 100644 index 0000000..32c9647 --- /dev/null +++ b/cocos-creator/3X/sdk/Selector.ts @@ -0,0 +1,191 @@ +import type AbstractDumper from "./AbstractDumper" +import type DefaultMatcher from "./DefaultMatcher" +import type AbstractNode from "./AbstractNode" + +export default class Selector { + dumper: AbstractDumper + matcher: DefaultMatcher + constructor(dumper: AbstractDumper, matcher: DefaultMatcher) { + this.dumper = dumper + this.matcher = matcher + } + + getRoot() { + return this.dumper.getRoot() + } + + select(cond: any, multiple: boolean) { + return this.selectImpl(cond, multiple, this.getRoot(), 9999, true, true) + } + + selectImpl( + cond: any, + multiple: boolean, + root: AbstractNode, + maxDepth: number, + onlyVisibleNode: boolean, + includeRoot: boolean + ) { + // 凡是visible为false后者parentVisible为false的都不选 + var result: any[] = [] + if (!root) { + return result + } + + var op = cond[0] + var args = cond[1] + + if (op === ">" || op === "/") { + var parents = [root] + for (var index in args) { + const i = parseInt(index) + var arg = args[i] + var midResult: any[] = [] + for (var j in parents) { + var parent = parents[j] + var _maxDepth = maxDepth + if (op === "/" && i !== 0) { + _maxDepth = 1 + } + var _res = this.selectImpl( + arg, + true, + parent, + _maxDepth, + onlyVisibleNode, + false + ) + for (var k in _res) { + if (midResult.indexOf(_res[k]) < 0) { + midResult.push(_res[k]) + } + } + } + parents = midResult + } + result = parents + } else if (op === "-") { + var query1 = args[0] + var query2 = args[1] + var result1 = this.selectImpl( + query1, + multiple, + root, + maxDepth, + onlyVisibleNode, + includeRoot + ) + for (var index in result1) { + var n = result1[index] + var sibling_result = this.selectImpl( + query2, + multiple, + n.getParent(), + 1, + onlyVisibleNode, + includeRoot + ) + for (var k in sibling_result) { + if (result.indexOf(sibling_result[k]) < 0) { + result.push(sibling_result[k]) + } + } + } + } else if (op === "index") { + var cond = args[0] + var i = args[1] + result = [ + this.selectImpl( + cond, + multiple, + root, + maxDepth, + onlyVisibleNode, + includeRoot + )[i], + ] + } else if (op === "^") { + // parent + // only select parent of the first matched UI element + var query1 = args[0] + var result1 = this.selectImpl( + query1, + false, + root, + maxDepth, + onlyVisibleNode, + includeRoot + ) + if (result1.length > 0) { + var parent_node = result1[0].getParent() + if (parent_node) { + result = [parent_node] + } + } + } else { + this._selectTraverse( + cond, + root, + result, + multiple, + maxDepth, + onlyVisibleNode, + includeRoot + ) + } + return result + } + + _selectTraverse( + cond: any, + node: AbstractNode, + outResult: any[], + multiple: boolean, + maxDepth: number, + onlyVisibleNode: boolean, + includeRoot: boolean + ) { + // 剪掉不可见节点branch + if (onlyVisibleNode && !node.getAttr("visible")) { + return false + } + + if (this.matcher.match(cond, node)) { + // 父子/祖先后代节点选择时,默认是不包含父节点/祖先节点的 + // 在下面的children循环中则需要包含,因为每个child在_selectTraverse中就当做是root + if (includeRoot) { + if (outResult.indexOf(node) < 0) { + outResult.push(node) + } + if (!multiple) { + return true + } + } + } + + // 最大搜索深度耗尽并不表示遍历结束,其余child节点仍需遍历 + if (maxDepth === 0) { + return false + } + maxDepth -= 1 + + var children = node.getChildren() + for (var i in node.getChildren()) { + var child = children[i] + var finished = this._selectTraverse( + cond, + child, + outResult, + multiple, + maxDepth, + onlyVisibleNode, + true + ) + if (finished) { + return true + } + } + + return false + } +} diff --git a/cocos-creator/README.md b/cocos-creator/README.md index f9c3722..1e50276 100644 --- a/cocos-creator/README.md +++ b/cocos-creator/README.md @@ -1,3 +1,7 @@ -## require cocos-creator 2.2.1 or higher +## For cocos-creator 2.X +Require cocos-creator 2.2.1 or higher. Use Poco folder. -[https://poco.readthedocs.io/en/latest/source/doc/integration.html](https://poco.readthedocs.io/en/latest/source/doc/integration.html) \ No newline at end of file +[https://poco.readthedocs.io/en/latest/source/doc/integration.html](https://poco.readthedocs.io/en/latest/source/doc/integration.html) + +## For cocos-creator 3.X +Work for cocos-creator 3.X. Use 3X folder. The way integration is the same with 2.X. \ No newline at end of file