Serialization work in progress

This commit is contained in:
barsdeveloper
2021-10-17 21:54:40 +02:00
parent 16fd34fa84
commit 9caea42101
29 changed files with 635 additions and 242 deletions

3
.gitignore vendored
View File

@@ -1 +1,2 @@
node_modules/
node_modules/
package-lock.json

File diff suppressed because one or more lines are too long

View File

@@ -2,14 +2,14 @@ import Utility from "./Utility"
import DragScroll from "./input/DragScroll"
import Select from "./input/Select"
import Zoom from "./input/Zoom"
import GraphEntity from "./GraphEntity"
import GraphEntity from "./graph/GraphEntity"
import BlueprintTemplate from "./template/BlueprintTemplate"
import GraphSelector from "./GraphSelector"
import GraphSelector from "./graph/GraphSelector"
/**
* @typedef {import("./GraphNode").default} GraphNode
* @typedef {import("./graph/GraphNode").default} GraphNode
*/
export default class UEBlueprint extends GraphEntity {
export default class Blueprint extends GraphEntity {
insertChildren() {
this.querySelector('[data-nodes]').append(...this.nodes)
@@ -26,7 +26,7 @@ export default class UEBlueprint extends GraphEntity {
this.viewportElement = null
/** @type {HTMLElement} */
this.overlayElement = null
/** @type {HTMLElement} */
/** @type {GraphSelector} */
this.selectorElement = null
/** @type {HTMLElement} */
this.nodesContainerElement = null
@@ -295,19 +295,19 @@ export default class UEBlueprint extends GraphEntity {
/**
*
* @param {...GraphNode} blueprintNodes
* @param {...GraphNode} graphNodes
*/
addNode(...blueprintNodes) {
[...blueprintNodes].reduce(
addNode(...graphNodes) {
[...graphNodes].reduce(
(s, e) => {
s.push(e)
return s
},
this.nodes)
if (this.nodesContainerElement) {
this.nodesContainerElement.append(...blueprintNodes)
this.nodesContainerElement.append(...graphNodes)
}
}
}
customElements.define('u-blueprint', UEBlueprint)
customElements.define('u-blueprint', Blueprint)

View File

@@ -1,17 +0,0 @@
export default class ETypesNames {
constructor(type) {
switch (type) {
case 'Class':
case 'None':
this.value = type
break
default:
throw new Error('Unexpected type name')
}
}
toString() {
return this.value
}
}

View File

@@ -1,70 +0,0 @@
import Guid from "./Guid";
export default class GraphPin {
constructor(Options) {
this.PinId = new Guid(Options?.PinId)
this.PinName = Options?.PinName ?? ""
this.PinToolTip = Options?.PinToolTip ?? ""
this.PinType = {
PinCategory: Options?.PinType?.PinCategory ?? "object",
PinSubCategory: Options?.PinType?.PinSubCategory ?? "",
PinSubCategoryMemberReference: Options?.PinType?.PinSubCategoryMemberReference ?? "",
PinValueType: Options?.PinType?.PinValueType ?? "",
PinValueType: Options?.PinType?.ContainerType ?? "None",
bIsReference: Options?.PinType?.bIsReference ?? false,
bIsConst: Options?.PinType?.bIsConst ?? false,
bIsWeakPointer: Options?.PinType?.bIsWeakPointer ?? false,
bIsUObjectWrapper: Options?.PinType?.bIsUObjectWrapper ?? false
}
this.LinkedTo = Options?.LinkedTo ?? null
this.DefaultValue = Options?.DefaultValue ?? true
this.AutogeneratedDefaultValue = Options?.AutogeneratedDefaultValue ?? false
this.PersistentGuid = Options?.PersistentGuid ?? null
this.bHidden = Options?.bHidden ?? false
this.bNotConnectable = Options?.bNotConnectable ?? false
this.bDefaultValueIsReadOnly = Options?.bDefaultValueIsReadOnly ?? false
this.bDefaultValueIsIgnored = Options?.bDefaultValueIsIgnored ?? false
this.bAdvancedView = Options?.bAdvancedView ?? false
this.bOrphanedPin = Options?.bOrphanedPin ?? false
}
static serializeValue(value) {
// No quotes
if (value === null) {
return '()'
}
if (value?.constructor?.name === 'Boolean') {
return value ? 'True' : 'False'
}
if (value?.constructor?.name === 'ETypesNames' || value?.constructor?.name === 'FGuid') {
return value.toString()
}
// Quotes
if (value?.constructor?.name === 'String') {
return `"${value}"`
}
}
static subSerialize(prefix, object) {
let result = ""
prefix += prefix != "" ? "." : ""
for (const property in object) {
if (object[property]?.constructor?.name === 'Object') {
result += GraphPin.subSerialize(prefix + property, object[property])
} else {
result += `${prefix + property}=${GraphPin.serializeValue(object[property])},`
}
}
return result
}
serialize() {
let result = `CustomProperties Pin (${this.constructor.subSerialize('', this)})`
return result
}
toString() {
return this.serialize()
}
}

View File

@@ -1,9 +1,11 @@
export default class Guid {
static generateGuid() {
static generateGuid(random) {
let result = ""
let random = new Uint32Array(4);
crypto.getRandomValues(random)
random.forEach(n => {
let values = new Uint32Array(4);
if (random === true) {
crypto.getRandomValues(values)
}
values.forEach(n => {
this.result += ('00000000' + n.toString(16).toUpperCase()).slice(-8)
})
return result
@@ -15,7 +17,7 @@ export default class Guid {
} else if (guid?.constructor?.name === 'FGuid') {
this.value = guid.value
} else {
this.value = Guid.generateGuid()
this.value = Guid.generateGuid(guid === true)
}
}

View File

@@ -6,4 +6,40 @@ export default class Utility {
static getScale(element) {
return getComputedStyle(element).getPropertyValue('--ueb-scale')
}
/**
* Sets a value in an object
* @param {String[]} keys The chained keys to access from object in order to set the value
* @param {any} value Value to be set
* @param {Object} target Object holding the data
* @param {Boolean} create Whether to create or not the key in case it doesn't exist
* @returns {Boolean} Returns true on succes, false otherwise
*/
static objectSet = (keys, value, target) => {
if (keys.length == 1) {
if (keys[0] in target) {
target[keys[0]] = value
return true
}
} else if (keys.length > 0) {
return Utility.objectSet(keys.slice(1), value, target[keys[0]])
}
return false
}
/**
* Gets a value from an object, gives defaultValue in case of failure
* @param {String[]} keys The chained keys to access from object in order to get the value
* @param {any} defaultValue Value to return in case from doesn't have it
* @param {Object} from Object holding the data
* @returns {any} The value in from corresponding to the keys or defaultValue otherwise
*/
static objectGet = (keys, defaultValue, from) => {
if (keys.length == 0 || !(keys[0] in from)) {
return defaultValue
}
return Utility.objectGet(keys.slice(1), defaultValue, from[keys[0]])
}
}

View File

@@ -0,0 +1,3 @@
export default class ObjectEntity {
}

38
js/entity/PinEntity.js Normal file
View File

@@ -0,0 +1,38 @@
import Guid from "../Guid";
import ReferenceTypeName from "../serialization/ReferenceTypeName";
import Utility from "../Utility"
export default class PinEntity {
static optionalKeys = ['Direction']
constructor(options = {}) {
const getOrFalse = (keys) => Utility.objectGet(keys, false, options)
const getOrEmptyString = (keys) => Utility.objectGet(keys, "", options)
this.PinId = new Guid(Utility.objectGet(["PinId"], true, options))
this.PinName = getOrEmptyString(["PinName"])
this.PinToolTip = getOrEmptyString(["PinToolTip"])
this.Direction = getOrEmptyString(["Direction"])
this.PinType = {
PinCategory: getOrEmptyString(["PinType", "PinCategory"]),
PinSubCategory: getOrEmptyString(["PinType", "PinSubCategory"]),
PinSubCategoryObject: Utility.objectGet(["PinType", "PinSubCategoryObject"], ReferenceTypeName.None, options),
PinSubCategoryMemberReference: Utility.objectGet(["PinType", "PinSubCategoryMemberReference"], null, options),
PinValueType: getOrFalse(["PinType", "PinValueType"]),
ContainerType: Utility.objectGet(["PinType", "ContainerType"], ReferenceTypeName.None, options),
bIsReference: getOrFalse(["PinType", "bIsReference"]),
bIsConst: getOrFalse(["PinType", "bIsConst"]),
bIsWeakPointer: getOrFalse(["PinType", "bIsWeakPointer"]),
bIsUObjectWrapper: getOrFalse(["PinType", "bIsUObjectWrapper"])
}
this.LinkedTo = Utility.objectGet(["LinkedTo"], null, options)
this.DefaultValue = getOrFalse(["DefaultValue"])
this.AutogeneratedDefaultValue = getOrFalse(["AutogeneratedDefaultValue"])
this.PersistentGuid = new Guid(getOrFalse(["PersistentGuid"]))
this.bHidden = getOrFalse(["bHidden"])
this.bNotConnectable = getOrFalse(["bNotConnectable"])
this.bDefaultValueIsReadOnly = getOrFalse(["bDefaultValueIsReadOnly"])
this.bDefaultValueIsIgnored = getOrFalse(["bDefaultValueIsIgnored"])
this.bAdvancedView = getOrFalse(["bAdvancedView"])
this.bOrphanedPin = getOrFalse(["bOrphanedPin"])
}
}

5
js/export.js Normal file
View File

@@ -0,0 +1,5 @@
import Blueprint from "./Blueprint"
import GraphNode from "./graph/GraphNode"
import PinSerializer from "./serialization/PinSerializer"
export { Blueprint as UEBlueprint, GraphNode as GraphNode, PinSerializer as PinSerializer }

View File

@@ -1,5 +0,0 @@
import UEBlueprint from "./UEBlueprint"
import GraphPin from "./GraphPin"
import GraphNode from "./GraphNode"
export { UEBlueprint as UEBlueprint, GraphNode as GraphNode, GraphPin as UGraphPin }

View File

@@ -4,11 +4,11 @@
export default class GraphEntity extends HTMLElement {
/**
*
* @param {import("./template/Template").default} template The template to render this node
* @param {import("../template/Template").default} template The template to render this node
*/
constructor(template) {
super()
/** @type {import("./UEBlueprint").default}" */
/** @type {import("../Blueprint").Blueprint}" */
this.blueprint = null
this.template = template
}

View File

@@ -1,6 +1,6 @@
import UBlueprintEntity from "./UBlueprintEntity"
export default class Link extends UBlueprintEntity {
export default class GraphLink extends UBlueprintEntity {
/**
*
@@ -22,8 +22,8 @@ export default class Link extends UBlueprintEntity {
<svg viewBox="0 0 100 100">
<line x1="0" y1="80" x2="100" y2="20" stroke="black" />
</svg>
`
`
}
}
customElements.define('u-link', Link)
customElements.define('u-link', GraphLink)

View File

@@ -1,11 +1,11 @@
import SelectableDraggable from "./SelectableDraggable"
import NodeTemplate from "./template/NodeTemplate"
import NodeTemplate from "../template/NodeTemplate"
export default class GraphNode extends SelectableDraggable {
constructor() {
super(new NodeTemplate())
this.graphNodeName = 'N/A'
this.graphNodeName = 'n/a'
this.inputs = []
this.outputs = []
}

View File

@@ -1,6 +1,6 @@
import FastSelectionModel from "./selection/FastSelectionModel";
import FastSelectionModel from "../selection/FastSelectionModel";
import GraphEntity from "./GraphEntity";
import Template from "./template/Template";
import Template from "../template/Template";
export default class GraphSelector extends GraphEntity {

View File

@@ -1,4 +1,4 @@
import Drag from "./input/Drag"
import Drag from "../input/Drag"
import GraphEntity from "./GraphEntity"
export default class SelectableDraggable extends GraphEntity {

View File

@@ -5,7 +5,7 @@ export default class MouseWheel extends Pointing {
/**
*
* @param {HTMLElement} target
* @param {import("../UEBlueprint").default} blueprint
* @param {import("../Blueprint").Blueprint} blueprint
* @param {Object} options
*/
constructor(target, blueprint, options) {

View File

@@ -5,7 +5,7 @@ export default class Pointing {
constructor(target, blueprint, options) {
/** @type {HTMLElement} */
this.target = target
/** @type {import("../UEBlueprint").default}" */
/** @type {import("../Blueprint").Blueprint}" */
this.blueprint = blueprint
this.movementSpace = this.blueprint?.getGridDOMElement() ?? document.documentElement
}

View File

@@ -0,0 +1,9 @@
import Serializer from "./Serializer";
export default class ObjectSerializer extends Serializer {
write(object) {
let result = `Pin (${this.constructor.subWrite('', this)})`
return result
}
}

View File

@@ -0,0 +1,57 @@
import Parsimmon from "parsimmon"
import PinEntity from "../entity/PinEntity"
import Utility from "../Utility"
import ReferenceTypeName from "./ReferenceTypeName"
let P = Parsimmon
export default class PinGrammar {
static pinEntity = new PinEntity()
Null = _ => P.string("()").map(() => null).desc("null value.")
None = _ => P.string("None").map(() => new ReferenceTypeName("None")).desc('None value')
ReferencePath = _ => P.regex(/[a-zA-Z_]/).sepBy1(P.string(".")).tieWith(".").sepBy1(Parsimmon.string("/")).tieWith("/")
Reference = r => r.Word.skip(P.optWhitespace).then(r.ReferencePath)
Word = _ => P.regex(/[a-zA-Z]+/)
Guid = _ => P.regex(/[0-9a-zA-Z]{32}/).desc("a 32 digit hexadecimal value")
String = _ => P.regex(/(?:[^"\\]|\\")*/).wrap(P.string('"'), P.string('"')).desc('a string value, with possibility to escape the quote symbol (") using \"')
Boolean = _ => P.string("True").or(P.string("False")).map(v => v === "True" ? true : false).desc("either True or False")
AttributeName = r => r.Word.sepBy1(P.string(".")).tieWith(".")
AttributeValue = r => P.alt(r.Null, r.None, r.Boolean, Reference, r.String, r.Guid)
Attribute = r => P.seqMap(
r.AttributeName,
P.string("=").trim(P.optWhitespace),
r.AttributeValue,
/**
*
* @param {String} name The key PinEntity
* @param {*} _
* @param {*} value
* @returns
*/
(name, _, value) =>
/**
* Sets the property name name in the object pinEntity to the value provided
* @param {PinEntity} pinEntity
*/
(pinEntity) => Utility.objectSet(name.split('.'), value, pinEntity)
)
Pin = r => {
return P.seqObj(
P.string("Pin"),
P.optWhitespace,
P.string("("),
[
"attributes",
r.Attribute
.trim(P.optWhitespace)
.sepBy(P.string(","))
.skip(P.regex(/,?/).then(P.optWhitespace)) // Optional trailing comma
],
P.string(')')
).map(object => {
let result = new PinEntity()
object.attributes.forEach(attributeSetter => attributeSetter(result))
return result
})
}
}

View File

@@ -0,0 +1,22 @@
import Parsimmon from "parsimmon"
import PinGrammar from "./PinGrammar"
import Serializer from "./Serializer"
export default class PinSerializer extends Serializer {
static pinGrammar = Parsimmon.createLanguage(new PinGrammar())
read(value) {
const parseResult = PinSerializer.pinGrammar.Pin.parse(value)
if (!parseResult.status) {
console.error("Error when trying to parse the pin.")
return parseResult
}
return parseResult.value
}
write(object) {
let result = `Pin (${Serializer.subWrite('', object)})`
return result
}
}

View File

@@ -0,0 +1,37 @@
export default class ReferenceTypeName {
/**
*
* @param {String} reference
* @returns
*/
static splitReference(reference) {
const pathBegin = reference.search(/['"]/)
const referenceType = reference.substr(0, pathBegin > 0 ? pathBegin : undefined) // reference example Class'"/Script/Engine.PlayerCameraManager"'
const referencePath = pathBegin > 0 ? reference.substr(pathBegin) : ""
switch (referenceType) {
case "None":
if (referencePath.length > 0) {
return false // None cannot have a path
}
default:
return [referenceType, referencePath]
}
}
/**
*
* @param {String} reference
*/
constructor(reference) {
reference = ReferenceTypeName.splitReference(reference)
if (!reference) {
throw new Error('Invalid reference: ' + reference)
}
this.reference = reference
}
toString() {
return this.value
}
}

View File

@@ -0,0 +1,51 @@
import Parsimmon from "parsimmon"
import PinGrammar from "./PinGrammar"
export default class Serializer {
static writeValue(value) {
if (value?.constructor?.name === 'Function') {
return this.writeValue(value())
}
// No quotes
if (value === null) {
return '()'
}
if (value?.constructor?.name === 'Boolean') {
return value ? 'True' : 'False'
}
if (value?.constructor?.name === 'ETypesNames' || value?.constructor?.name === 'FGuid') {
return value.toString()
}
// Quotes
if (value?.constructor?.name === 'String') {
return `"${value}"`
}
}
static subWrite(prefix, object) {
let result = ""
prefix += prefix != "" ? "." : ""
const fullPropertyName = prefix + property
for (const property in object) {
if (object[property]?.constructor?.name === 'Object') {
result += Serializer.subWrite(fullPropertyName, object[property])
} else if (!object.constructor.optionalKeys.contains(fullPropertyName)) {
result += `${fullPropertyName}=${Serializer.writeValue(object[property])},`
}
}
return result
}
/**
*
* @param {String} value
*/
read(value) {
//Parsimmon.length
}
write(object) {
return ''
}
}

View File

@@ -0,0 +1,15 @@
import ObjectEntity from "../entity/ObjectEntity";
import PinEntity from "../entity/PinEntity";
import SerializeObject from "./ObjectSerialize";
import PinSerializer from "./PinSerializer";
export default class SerializerFactory {
static serializers = new Map([
[PinEntity.prototype.constructor.name, PinSerializer],
[ObjectEntity.prototype.constructor.name, SerializeObject]
])
createSerializer(object) {
return SerializerFactory.serializers.get(object.constructor.name)
}
}

View File

@@ -17,7 +17,7 @@ export default class BlueprintTemplate extends Template {
/**
*
* @param {import("../UEBlueprint").default} element
* @param {import("../Blueprint").Blueprint} element
* @returns
*/
viewport(element) {

View File

@@ -1,5 +1,5 @@
/**
* @typedef {import("./GraphNode").default} GraphNode
* @typedef {import("../graph/GraphNode").default} GraphNode
*/
export default class Template {

View File

@@ -2,7 +2,7 @@
"name": "ueblueprint",
"version": "1.0.0",
"description": "Unreal Engine's Blueprint visualisation library",
"main": "index.js",
"main": "ueblueprint.js",
"scripts": {
"build": "rollup --config"
},
@@ -21,6 +21,14 @@
"url": "https://github.com/barsdeveloper/ueblueprint/issues"
},
"homepage": "https://github.com/barsdeveloper/ueblueprint#readme",
"dependencies": {},
"devDependencies": {}
}
"devDependencies": {
"@rollup/plugin-commonjs": "^21.0.0",
"@rollup/plugin-node-resolve": "^13.0.5",
"rollup": "^2.58.0",
"rollup-plugin-terser": "^7.0.2",
"terser": "^5.9.0"
},
"dependencies": {
"parsimmon": "^1.18.0"
}
}

View File

@@ -1,7 +1,16 @@
import { nodeResolve } from '@rollup/plugin-node-resolve'
import commonjs from '@rollup/plugin-commonjs'
import { terser } from 'rollup-plugin-terser'
export default {
input: 'js/exporting.js',
input: 'js/export.js',
output: {
file: 'ueblueprint.js',
format: 'es',
file: 'dist/ueblueprint.js',
format: 'es'
},
};
plugins: [
nodeResolve({ browser: true }),
commonjs(),
//terser()
]
}

View File

@@ -14,7 +14,7 @@
<body>
<div>Hello</div>
<script type="module">
import { UEBlueprint, GraphNode, UGraphPin } from "./ueblueprint.js"
import { UEBlueprint, GraphNode, PinSerializer } from "./dist/ueblueprint.js"
let blueprint = new UEBlueprint()
let node0 = new GraphNode(); node0.setLocation([985, 393]); let node1 = new GraphNode(); node1.setLocation([999, 114]); let node2 = new GraphNode(); node2.setLocation([811, 253]); let node3 = new GraphNode(); node3.setLocation([802, 146]); let node4 = new GraphNode(); node4.setLocation([597, 105]); let node5 = new GraphNode(); node5.setLocation([789, 233]); let node6 = new GraphNode(); node6.setLocation([549, 289]); let node7 = new GraphNode(); node7.setLocation([678, 193]); let node8 = new GraphNode(); node8.setLocation([1078, 244]); let node9 = new GraphNode(); node9.setLocation([751, 151]); let node10 = new GraphNode(); node10.setLocation([1046, -14]); let node11 = new GraphNode(); node11.setLocation([714, 267]); let node12 = new GraphNode(); node12.setLocation([767, 36]); let node13 = new GraphNode(); node13.setLocation([807, 219]); let node14 = new GraphNode(); node14.setLocation([1031, 70]); let node15 = new GraphNode(); node15.setLocation([906, 389]); let node16 = new GraphNode(); node16.setLocation([936, 131]); let node17 = new GraphNode(); node17.setLocation([689, 249]); let node18 = new GraphNode(); node18.setLocation([1153, 343]); let node19 = new GraphNode(); node19.setLocation([626, 209]); blueprint.addNode(node0, node1, node2, node3, node4, node5, node6, node7, node8, node9, node10, node11, node12, node13, node14, node15, node16, node17, node18, node19);