Compare commits

..

18 Commits

Author SHA1 Message Date
barsdeveloper
5d847ab8f6 Several fixes 2024-07-18 17:35:29 +02:00
barsdeveloper
8a2cd6c26e Fixing still 2024-07-17 21:38:10 +02:00
barsdeveloper
e0d8990e6a Decoding code fixes 2024-07-16 21:36:13 +02:00
barsdeveloper
98ebdd78b2 Better typing for grammars 2024-06-06 23:16:21 +02:00
barsdeveloper
ad4ba2c46d WIP 2024-06-06 20:10:17 +02:00
barsdeveloper
6d99db5fd1 CustomProperties creating fixed 2024-06-05 16:30:14 +02:00
barsdeveloper
beccfe522a Fix Function reference 2024-06-05 15:58:55 +02:00
barsdeveloper
ad8305ca52 Remove serialization bits 2024-06-05 00:28:28 +02:00
barsdeveloper
6ca966e176 Entities tests fixed 2024-06-04 22:54:16 +02:00
barsdeveloper
e16822760f Simple entities serialization fixed 2024-06-04 14:40:47 +02:00
barsdeveloper
c05e6d3cc9 More entities fixed 2024-06-04 01:00:41 +02:00
barsdeveloper
1a8636bb5d More tests and fixed 2024-06-03 12:42:42 +02:00
barsdeveloper
8fed17b20f Tests for various entity classes and update entity class implementations 2024-06-03 00:11:30 +02:00
barsdeveloper
5314228b33 Fix format text entity 2024-06-01 16:27:17 +02:00
barsdeveloper
8258572e56 ArrayEntity parsing fixed 2024-05-31 18:39:58 +02:00
barsdeveloper
ecc71b76d1 WIP 2024-05-31 15:09:48 +02:00
barsdeveloper
1c2778fbf8 Still WIP 2024-05-28 16:44:39 +02:00
barsdeveloper
70b4cabb97 Merge remote-tracking branch 'origin/master' into refactoring-entities 2024-05-21 15:52:34 +02:00
364 changed files with 8486 additions and 13170 deletions

0
.gitignore vendored Executable file → Normal file
View File

0
.vscode/extensions.json vendored Executable file → Normal file
View File

35
.vscode/launch.json vendored Normal file
View File

@@ -0,0 +1,35 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "Launch index.html",
"type": "firefox",
"request": "launch",
"reAttach": true,
"file": "${workspaceFolder}/index.html"
},
{
"name": "Launch localhost",
"type": "firefox",
"request": "launch",
"reAttach": true,
"url": "http://localhost/index.html",
"webRoot": "${workspaceFolder}"
},
{
"name": "Attach",
"type": "firefox",
"request": "attach"
},
{
"name": "Launch WebExtension",
"type": "firefox",
"request": "launch",
"reAttach": true,
"addonPath": "${workspaceFolder}"
}
]
}

0
.vscode/settings.json vendored Executable file → Normal file
View File

11
CONTRIBUTING.md Executable file → Normal file
View File

@@ -1,7 +1,7 @@
# Development Guide
Getting started with the development of this application is very easy. It has a (arguably) well defined object-oriented architecture and separation of concerns.
Getting started with the development of this application is very easy because it has a (arguably) well defined object-oriented architecture and separation of concerns.
Before starting, the gentle reader might want to make sure to be familiar with the [Lit](https://lit.dev/) library and its element [lifecycle](https://lit.dev/docs/components/lifecycle/). This library is used extensively throught the application to keep the HTML elements in sync with the data and avoid updating the DOM too often. The original author is aware that there are way more popular alternatives out there like React, Vue and Svelte, but the design of Lit fits very well into the original design of this application: vanilla JavaScript and object-oriented. This allowed the introduction of Lit with a relatively small amount of changes to the existing code because the decision to use Lit was made at a later point in time. One important detail is that it does not make use of the shadow DOM (part of the Web Components), the real reason is that the development started without using Lit but it is still nice to be able to have a global CSS style (which wouldn't be possibile with a shadow root) so that restyling the library is just a matter of adding another CSS file and rewrite a few properties.
Before starting, the gentle reader might want to make sure to be familiar with the [Lit](https://lit.dev/) library and its element [lifecycle](https://lit.dev/docs/components/lifecycle/). This library is used extensively throught the application to keep the HTML elements in sync with the data and avoid updating the DOM too often. The original author is aware that there are way more popular alternatives out there like React, Vue and Svelte, but the design of Lit fits very well into the original design of this application: vanilla JavaScript and object-oriented. This allowed the introduction of Lit with a relatively small amount of changes to the existing code, yes because the decision to use Lit was made at a later point in time. One important detail is that it does not make use of the shadow DOM (part of the Web Components), the real reason is that the development started without using Lit but it is still nice to be able to have a global CSS style (which wouldn't be possibile with a shadow root) so that restyling the library is just a matter of adding another CSS file and rewrite a few properties.
The only other external library that is used here is [Parsernostrum](https://github.com/barsdeveloper/parsernostrum): a very small but capable text parsing library used to deserialize the text produced by the UE Blueprint Editor.
@@ -47,7 +47,12 @@ And refresh the HTML page possibly holding `Shift`.
There are a few concepts that must be assimilated in order to understand the design. Those concepts are in general each mapped into a subfolder in `js/`.
### Entity
An Entity is just a data holder object that does not really do anything by itself, it has a purely information storage related purpose. The top class at the hierarchy of entities is `IEntity`. It is responsible for the initialization of the entity in its constructor according to the information contained in the object provided as an argument or from the attributes static field. This ended up being a somewhat wacky runtime type system. Each subclass can specify its attributes in the form of a static member variable of type vanilla object where each entry is a subclass of IEntity. The entities moreover are responsible for their serialization and deserialization: each class has a `serialize()` method and a `grammar` attribute to parse entities.
An Entity is just a data holder object that does not really do anything by itself, it has a purely information storage related purpose. The top class at the hierarchy of entities is `IEntity`. This one is a bit more complicated in the sense that it does the initialization of the entity in its constructor according to the information contained in the object provided as an argument or from the attributes static field. This ended up being a somewhat wacky runtime type system. Each subclass can specify its attributes static member variable of type object where each entry is either a value (the default value the attribute will have), a function (called to generate such default value) or an object and in that case it will be of type `AttributeInformation` (please note that in case one wants to assign as default value a specific object, the solution is either to wrap it into a `AttributeInformation` object or to return it from a function).
### Grammar and Serializer
In the `serialization/` folder the gentle reader will find all the classes responsible for transforming entities from and to text that the UE Blueprint Enditor can understand. One important class here is `Grammar` that contains similar formal grammar rules that use the [Parsernostrum library](https://github.com/barsdeveloper/parsernostrum) to create entities from Blueprint text. `Serializer` is at the top of the serializer classes hierarchy and it uses a factory design pattern to register serializers for the various entities types (check `js/serialization/initializeSerializerFactory.js`). It does both read and write of entities: to read it will use the Grammar after creating a language using a function from Parsernostrum, to write it will use methods from the class itself.
Grammar is usually the first place to look when pasting valid Blueprint code does fail. Most likely newer version of Unreal Engine did add some new data type that was not implemented yet (or this library never managed to handle it in the first place). In that case the approach should be trying to fix the existing grammar and entities to accept it, then implement the new entities and attributes.
### Element
Each element is just a custom HTML element type and its tag name is defined in the class file. The top level of the hierarchy is `IElement` and it inherits from `LitElement`. This class can be thought as an association between an entity and a template (and those are the arguments of the constructor). The top class `IElement` does propagate the lifecycle provided by `LitElement` to the template so that a template can hook into it.

0
LICENSE Executable file → Normal file
View File

26
README.md Executable file → Normal file
View File

@@ -1,22 +1,22 @@
# UEBlueprint
A stand alone implementation of the UE's Blueprint visual language editor
https://www.npmjs.com/package/ueblueprint
A stand alone editor implementation of the UE's Blueprint visual language. (WIP)
## Features:
- Intercommunicates with UE (can copy nodes both ways).
- Can be used as a WEB library to visualize and interact with Blueprint graphs.
- Can be used as a WEB library to visualize, interact and modify Blueprint graphs.
- Can be used inside VS Code to operate directly on files from a UE project (in the future).
- Graph shown is pixel-similar to how it appears in UE.
- Graph behaves the same way as it does in UE (with the default settings).
- All the information shown in he graph is just the one embedded in the serialized text.
- All the information shown in he graph is just the one embedded in the serialized text (in VS Code it should be able to access assets also).
- Modern object oriented, clean, JavaScript codebase.
## Demo:
[Try it!](https://barsdeveloper.github.io/ueblueprint/)
### [Try it!](https://barsdeveloper.github.io/ueblueprint/)
![img1](https://github.com/barsdeveloper/ueblueprint/assets/84736467/022704e7-2c9f-4595-9513-cd7770961e0d)
[![img1](https://github.com/barsdeveloper/ueblueprint/assets/84736467/022704e7-2c9f-4595-9513-cd7770961e0d)](https://barsdeveloper.github.io/ueblueprint/)
## Getting started:
@@ -33,9 +33,8 @@ npx http-server
### Use in a web page
You can check `index.html` for a working example, the main steps are the following:
1. Make the `dist` directory available in your website by copying it or installing through npm `npm i ueblueprint`.
2. Include `dist/css/ueb-style.min.css` stylesheet in your page.
3. Define eventual CSS variables.
1. Include `dist/css/ueb-style.css` stylesheet in your page.
2. Define eventual CSS variables.
```HTML
<style>
ueb-blueprint {
@@ -43,14 +42,13 @@ You can check `index.html` for a working example, the main steps are the followi
}
</style>
```
4. Import the class Blueprint in JavaScript (this library uses modules).
3. Import the class Blueprint in JavaScript (this library uses modules).
```HTML
<script type="module">
import { Blueprint } from "./dist/ueblueprint.min.js"
import { Blueprint } from "./dist/ueblueprint.js"
</script>
```
5. Define your blueprint by writing the code inside a `template`, inside a `ueb-blueprint` element.
It can have light background using the following CSS class: `<ueb-blueprint class="ueb-light-mode">`
4. Define your blueprint the by just writing the code inside a `ueb-blueprint`, inside a `template` element.
```HTML
<ueb-blueprint>
<template>

0
assets/fonts/roboto-bold.woff2 Executable file → Normal file
View File

0
assets/fonts/roboto-condensed-bold.woff2 Executable file → Normal file
View File

0
assets/fonts/roboto-light.woff2 Executable file → Normal file
View File

0
assets/fonts/roboto-regular.woff2 Executable file → Normal file
View File

135
dist/css/ueb-style.css vendored Executable file → Normal file
View File

@@ -11,9 +11,6 @@
ueb-blueprint {
--ueb-scale: 1;
--ueb-grid-actual-size: var(--ueb-grid-size);
--ueb-grid-line-color: #353535;
--ueb-grid-set-line-color: #161616;
--ueb-grid-axis-line-color: #000;
display: block;
position: relative;
font-family: Roboto, Noto, Oxygen, Ubuntu, "Open Sans", "Helvetica Neue", sans-serif;
@@ -23,8 +20,7 @@ ueb-blueprint {
}
ueb-blueprint svg {
overflow: visible !important;
max-width: none !important;
overflow: visible;
}
.ueb-viewport-header {
@@ -37,15 +33,6 @@ ueb-blueprint svg {
z-index: 1;
}
.ueb-viewport-about {
margin-top: -6px;
padding: 0 8px;
align-self: center;
font-size: 20px;
font-weight: bolder;
cursor: pointer;
}
@keyframes ueb-zoom-animation {
0% {
color: #7f7f7f;
@@ -55,7 +42,7 @@ ueb-blueprint svg {
}
}
.ueb-zoom-changed .ueb-viewport-zoom {
animation: 1500ms ueb-zoom-animation;
animation: 600ms ueb-zoom-animation;
}
.ueb-viewport-zoom {
@@ -102,6 +89,7 @@ ueb-blueprint[data-scrolling=false] .ueb-grid {
ueb-blueprint.ueb-zoom--4 {
--ueb-grid-actual-size: calc(var(--ueb-grid-size) * 2);
--ueb-node-radius: 0 !important;
}
ueb-blueprint.ueb-zoom--6 {
@@ -156,50 +144,6 @@ ueb-selector > * {
overflow: hidden;
}
.ueb-info-dialog {
border: 6px solid #202020;
padding: 5px 30px;
background: #121212;
color: #f0f0f0;
box-shadow: 0 2px 10px 0 #000;
}
.ueb-info-dialog a:link,
.ueb-info-dialog a:visited,
.ueb-info-dialog a:hover,
.ueb-info-dialog a:active {
outline: none;
color: #f0f0f0;
}
.ueb-info-dialog::backdrop {
background-color: rgba(0, 0, 0, 0.6274509804);
backdrop-filter: blur(4px);
}
/* Light mode */
ueb-blueprint.ueb-light-mode {
--ueb-grid-line-color: #ddd;
--ueb-grid-set-line-color: #c0c0c0;
--ueb-grid-axis-line-color: #a0a0a0;
}
ueb-blueprint.ueb-light-mode .ueb-grid {
background-color: #f6f6f6;
}
ueb-blueprint.ueb-light-mode ueb-node.ueb-node-style-glass .ueb-node-wrapper {
background-color: rgba(0, 0, 0, 0.5647058824);
}
ueb-blueprint.ueb-light-mode ueb-link .ueb-link-path {
filter: saturate(200%) brightness(0.6);
}
ueb-blueprint.ueb-light-mode .ueb-viewport-header {
background: rgba(0, 0, 0, 0.3);
color: #101010;
}
ueb-node.ueb-node-style-minimal {
box-shadow: none;
}
@@ -231,7 +175,9 @@ ueb-node.ueb-node-style-minimal[data-selected=true] .ueb-node-border {
ueb-link {
position: absolute;
--ueb-link-color: rgb(var(--ueb-link-color-rgb));
--ueb-from-input-coefficient: calc(2 * var(--ueb-from-input) - 1);
/* when from-y > to-y */
--ueb-y-reflected: clamp(0, var(--ueb-from-y) - var(--ueb-to-y) - 1, 1);
display: block;
margin-left: calc(var(--ueb-link-start) * -1px);
min-width: calc(var(--ueb-link-min-width) * 1px);
@@ -243,11 +189,12 @@ ueb-link {
}
ueb-link > svg {
--ueb-y-reflected-coefficient: calc(2 * var(--ueb-y-reflected) - 1);
position: absolute;
width: 100% !important;
height: 100% !important;
min-height: 1px !important;
transform: scaleX(var(--ueb-link-scale-x)) scaleY(var(--ueb-link-scale-y));
width: 100%;
height: 100%;
min-height: 1px;
transform: scaleY(calc(var(--ueb-y-reflected-coefficient) * var(--ueb-from-input-coefficient)));
z-index: 1;
}
@@ -274,12 +221,10 @@ ueb-link[data-dragging=true] .ueb-link-message {
}
.ueb-link-message {
--ueb-link-message-top: calc(50% * (var(--ueb-link-scale-y) + 1) + 22px);
--ueb-link-message-left: calc(100% - var(--ueb-start-percentage) + 15px);
display: none;
position: absolute;
top: var(--ueb-link-message-top);
left: var(--ueb-link-message-left);
top: calc(100% * (1 - var(--ueb-y-reflected)) + 22px);
left: calc((1 - var(--ueb-from-input)) * 100% + (var(--ueb-from-input-coefficient)) * var(--ueb-start-percentage) + 15px);
border: 1px solid #000;
border-radius: 2px;
background: linear-gradient(to bottom, #2a2a2a 0, #151515 50%, #2a2a2a 100%);
@@ -288,11 +233,6 @@ ueb-link[data-dragging=true] .ueb-link-message {
z-index: 1000000;
}
ueb-link[data-from-input=true] .ueb-link-message {
--ueb-link-message-top: calc(-50% * (var(--ueb-link-scale-y) - 1) + 22px);
--ueb-link-message-left: calc(var(--ueb-start-percentage) + 15px);
}
.ueb-link-message-icon {
display: inline-block;
padding: 4px;
@@ -326,6 +266,10 @@ ueb-node.ueb-node-style-comment {
min-width: 0;
}
.ueb-zoom--2 ueb-node {
box-shadow: none;
}
.ueb-node-border {
margin: -3px;
padding: 3px;
@@ -365,10 +309,6 @@ ueb-node[data-selected=true] > .ueb-node-border {
overflow: hidden;
}
.ueb-zoom--2 .ueb-node-wrapper {
background: rgb(14, 16, 10);
}
ueb-node.ueb-node-style-operation .ueb-node-wrapper {
grid-template-rows: min-content auto auto min-content min-content;
grid-template-columns: 50% 0% 1fr;
@@ -394,6 +334,7 @@ ueb-blueprint[data-scrolling=false][data-selecting=false] .ueb-draggable {
}
.ueb-zoom--2 .ueb-node-wrapper {
box-shadow: none;
padding: 0;
background: #101010;
}
@@ -430,6 +371,10 @@ ueb-node.ueb-node-style-event .ueb-node-top {
background: linear-gradient(rgba(255, 255, 255, 0.1882352941) 1px, transparent 1px), linear-gradient(170deg, rgb(var(--ueb-node-color)) 0%, rgb(var(--ueb-node-color)) 50%, transparent 100%);
}
.ueb-zoom--2 .ueb-node-top {
box-shadow: none;
}
.ueb-zoom--2 .ueb-node-style-default .ueb-node-top {
background: rgb(var(--ueb-node-color));
}
@@ -578,10 +523,6 @@ ueb-node.ueb-node-style-glass .ueb-node-wrapper {
background-repeat: repeat, no-repeat;
}
.ueb-zoom--2 ueb-node.ueb-node-style-glass .ueb-node-wrapper {
background: #101010;
}
ueb-node.ueb-node-style-glass .ueb-node-name {
padding-right: 0;
padding-left: 0;
@@ -705,7 +646,7 @@ ueb-node.ueb-node-style-conversion .ueb-node-wrapper::after {
grid-area: center;
align-self: center;
justify-self: center;
margin: 10px 10px 10px -6px;
margin: 10px;
width: 6px;
height: 6px;
border-radius: 3px;
@@ -799,6 +740,11 @@ ueb-pin[data-connectable=false] .ueb-pin-icon {
visibility: hidden;
}
.ueb-node-style-event ueb-pin[data-type=delegate] .ueb-pin-icon {
width: 11px;
height: 11px;
}
.ueb-node-inputs .ueb-pin-icon {
margin-right: 6px;
}
@@ -869,23 +815,6 @@ ueb-node[data-type="/Script/BlueprintGraph.K2Node_VariableSet"] ueb-pin[data-dir
outline: none;
}
.ueb-pin-input[type=checkbox] {
display: grid;
place-content: center;
appearance: none;
width: 18px;
height: 18px;
border: 1px solid #353535;
background: #0f0f0f;
}
.ueb-pin-input[type=checkbox]:checked::before {
content: "";
height: 0.7em;
width: 0.8em;
clip-path: polygon(14% 44%, 0 65%, 50% 100%, 100% 16%, 80% 0%, 43% 62%);
background: #0070e0;
}
ueb-pin[data-linked=true] .ueb-pin-input,
ueb-pin[data-linked=true] .ueb-pin-input-wrapper {
display: none;
@@ -936,7 +865,7 @@ ueb-pin[data-type="/Script/CoreUObject.LinearColor"] .ueb-pin-input {
max-height: 16em;
/* 97% is to get an effective font size of 12.6px from --ueb-font-size which is 13 px by default */
font-size: 97%;
white-space: pre;
white-space: nowrap;
background: none;
color: inherit;
overflow: auto;
@@ -1351,12 +1280,4 @@ ueb-ui-slider {
color: #c0c0c0;
}
/* To improve a bit performance */
.ueb-zoom--2 * {
border: none !important;
border-radius: 0 !important;
box-shadow: none !important;
text-shadow: none !important;
}
/*# sourceMappingURL=ueb-style.css.map */

2
dist/css/ueb-style.css.map vendored Executable file → Normal file

File diff suppressed because one or more lines are too long

2
dist/css/ueb-style.min.css vendored Executable file → Normal file

File diff suppressed because one or more lines are too long

2
dist/css/ueb-style.min.css.map vendored Executable file → Normal file

File diff suppressed because one or more lines are too long

0
dist/font/roboto-bold.woff vendored Executable file → Normal file
View File

0
dist/font/roboto-bold.woff2 vendored Executable file → Normal file
View File

0
dist/font/roboto-condensed-bold.woff2 vendored Executable file → Normal file
View File

0
dist/font/roboto-condensed-regular.woff2 vendored Executable file → Normal file
View File

0
dist/font/roboto-light.woff vendored Executable file → Normal file
View File

0
dist/font/roboto-light.woff2 vendored Executable file → Normal file
View File

0
dist/font/roboto-regular.woff vendored Executable file → Normal file
View File

0
dist/font/roboto-regular.woff2 vendored Executable file → Normal file
View File

5247
dist/ueblueprint.js vendored

File diff suppressed because one or more lines are too long

12
dist/ueblueprint.min.js vendored Executable file → Normal file

File diff suppressed because one or more lines are too long

View File

@@ -11,6 +11,7 @@
padding: 0;
--ueb-height: 100vh;
}
</style>
</head>

View File

@@ -4,7 +4,7 @@
<head>
<meta charset="utf-8" />
<title>UE Blueprint</title>
<link rel="stylesheet" href="dist/css/ueb-style.min.css">
<link rel="stylesheet" href="dist/css/ueb-style.css">
<style>
body {
margin: 0;

View File

@@ -4,8 +4,6 @@ import IElement from "./element/IElement.js"
import LinkElement from "./element/LinkElement.js"
import NodeElement from "./element/NodeElement.js"
import BlueprintEntity from "./entity/BlueprintEntity.js"
import BooleanEntity from "./entity/BooleanEntity.js"
import NiagaraClipboardContent from "./entity/objects/NiagaraClipboardContent.js"
import BlueprintTemplate from "./template/BlueprintTemplate.js"
/** @extends {IElement<BlueprintEntity, BlueprintTemplate>} */
@@ -16,19 +14,19 @@ export default class Blueprint extends IElement {
type: Boolean,
attribute: "data-selecting",
reflect: true,
converter: BooleanEntity.booleanConverter,
converter: Utility.booleanConverter,
},
scrolling: {
type: Boolean,
attribute: "data-scrolling",
reflect: true,
converter: BooleanEntity.booleanConverter,
converter: Utility.booleanConverter,
},
focused: {
type: Boolean,
attribute: "data-focused",
reflect: true,
converter: BooleanEntity.booleanConverter,
converter: Utility.booleanConverter,
},
zoom: {
type: Number,
@@ -298,11 +296,29 @@ export default class Blueprint extends IElement {
return [x, y]
}
getNodes(selected = false) {
getNodes(
selected = false,
[t, r, b, l] = [
Number.MIN_SAFE_INTEGER,
Number.MAX_SAFE_INTEGER,
Number.MAX_SAFE_INTEGER,
Number.MIN_SAFE_INTEGER,
]
) {
let result = this.nodes
if (selected) {
result = result.filter(n => n.selected)
}
if (
t > Number.MIN_SAFE_INTEGER
|| r < Number.MAX_SAFE_INTEGER
|| b < Number.MAX_SAFE_INTEGER
|| l > Number.MIN_SAFE_INTEGER
) {
result = result.filter(n => {
return n.topBoundary() >= t && n.rightBoundary() <= r && n.bottomBoundary() <= b && n.leftBoundary() >= l
})
}
return result
}
@@ -337,25 +353,25 @@ export default class Blueprint extends IElement {
getLinks(a = null, b = null) {
if ((a == null) != (b == null)) {
const pin = a ?? b
return this.links.filter(link => link.origin == pin || link.target == pin)
return this.links.filter(link => link.source == pin || link.destination == pin)
}
if (a != null && b != null) {
return this.links.filter(link =>
link.origin == a && link.target == b
|| link.origin == b && link.target == a
link.source == a && link.destination == b
|| link.source == b && link.destination == a
)
}
return this.links
}
/**
* @param {PinElement} originPin
* @param {PinElement} targetPin
* @param {PinElement} sourcePin
* @param {PinElement} destinationPin
*/
getLink(originPin, targetPin, strictDirection = false) {
getLink(sourcePin, destinationPin, strictDirection = false) {
return this.links.find(link =>
link.origin == originPin && link.target == targetPin
|| !strictDirection && link.origin == targetPin && link.target == originPin
link.source == sourcePin && link.destination == destinationPin
|| !strictDirection && link.source == destinationPin && link.destination == sourcePin
)
}
@@ -367,22 +383,6 @@ export default class Blueprint extends IElement {
this.getNodes().forEach(node => Blueprint.nodeSelectToggleFunction(node, false))
}
getSerializedText() {
const nodes = this.blueprint.getNodes(true).map(n => n.entity)
let exports = false
let result = nodes
.filter(n => {
exports ||= n.exported
return !n.exported
})
.reduce((acc, cur) => acc + cur.serialize(), "")
if (exports) {
const object = new NiagaraClipboardContent(this.blueprint.entity, nodes)
result = object.serialize() + result
}
return result
}
/** @param {...IElement} graphElements */
addGraphElement(...graphElements) {
/** @param {CustomEvent} event */
@@ -409,22 +409,17 @@ export default class Blueprint extends IElement {
for (const element of graphElements) {
element.blueprint = this
if (element instanceof NodeElement && !this.nodes.includes(element)) {
const name = element.entity.getObjectName()
this.entity.updateNameIndex(name)
if (element.getType() == Configuration.paths.niagaraClipboardContent) {
this.entity = this.entity.mergeWith(element.entity)
const additionalSerialization = atob(element.entity.ExportedNodes?.toString() ?? "")
if (additionalSerialization) {
this.template.getPasteInputObject().pasted(additionalSerialization)
.forEach(node => node.entity.exported = true)
}
const additionalSerialization = atob(element.entity.ExportedNodes)
this.template.getPasteInputObject().pasted(additionalSerialization)
.forEach(node => node.entity._exported = true)
continue
}
const name = element.entity.getObjectName()
const homonym = this.entity.getHomonymObjectEntity(element.entity)
if (homonym) {
const newName = this.entity.takeFreeName(name)
// @ts-expect-error
homonym.Name = new (homonym.Name.constructor)(newName)
homonym.Name = this.entity.takeFreeName(name)
}
this.nodes.push(element)
this.entity.addObjectEntity(element.entity)

View File

@@ -1,7 +1,6 @@
import { css } from "lit"
export default class Configuration {
static VERSION = "2.0.0"
static nodeColors = {
black: css`20, 20, 20`,
blue: css`84, 122, 156`,
@@ -24,7 +23,7 @@ export default class Configuration {
static colorWindowName = "Color Picker"
static defaultCommentHeight = 96
static defaultCommentWidth = 400
static distanceThreshold = 20 // px
static distanceThreshold = 5 // px
static dragEventName = "ueb-drag"
static dragGeneralEventName = "ueb-drag-general"
static edgeScrollThreshold = 50
@@ -38,9 +37,12 @@ export default class Configuration {
end: "blueprint-unfocus",
}
static fontSize = css`13px`
static gridAxisLineColor = css`black`
static gridExpandThreshold = 0.25 // remaining size factor threshold to cause an expansion event
static gridLineColor = css`#353535`
static gridLineWidth = 1 // px
static gridSet = 8
static gridSetLineColor = css`#161616`
static gridShrinkThreshold = 4 // exceding size factor threshold to cause a shrink event
static gridSize = 16 // px
static hexColorRegex = /^\s*#(?<r>[0-9a-fA-F]{2})(?<g>[0-9a-fA-F]{2})(?<b>[0-9a-fA-F]{2})([0-9a-fA-F]{2})?|#(?<rs>[0-9a-fA-F])(?<gs>[0-9a-fA-F])(?<bs>[0-9a-fA-F])\s*$/
@@ -77,13 +79,9 @@ export default class Configuration {
* @param {Number} c1
* @param {Number} c2
*/
static linkRightSVGPath = (start, c1, c2, arc = false) => {
const end = 100 - start
const mid = arc
? 50 + (c2 - start)
: 50
const fin = arc ? end + c1 - start : end - c1 + start
return `M ${start} 0 C ${c1.toFixed(2)} 0, ${c2.toFixed(2)} 0, ${mid.toFixed(2)} 50 S ${fin.toFixed(2)} 100, `
static linkRightSVGPath = (start, c1, c2) => {
let end = 100 - start
return `M ${start} 0 C ${c1.toFixed(3)} 0, ${c2.toFixed(3)} 0, 50 50 S ${(end - c1 + start).toFixed(3)} 100, `
+ `${end.toFixed(3)} 100`
}
static maxZoom = 7
@@ -108,7 +106,6 @@ export default class Configuration {
callArrayFunction: "/Script/BlueprintGraph.K2Node_CallArrayFunction",
callDelegate: "/Script/BlueprintGraph.K2Node_CallDelegate",
callFunction: "/Script/BlueprintGraph.K2Node_CallFunction",
clearDelegate: "/Script/BlueprintGraph.K2Node_ClearDelegate",
comment: "/Script/UnrealEd.EdGraphNode_Comment",
commutativeAssociativeBinaryOperator: "/Script/BlueprintGraph.K2Node_CommutativeAssociativeBinaryOperator",
componentBoundEvent: "/Script/BlueprintGraph.K2Node_ComponentBoundEvent",
@@ -131,7 +128,6 @@ export default class Configuration {
eTextureMipValueMode: "/Script/Engine.ETextureMipValueMode",
eTraceTypeQuery: "/Script/Engine.ETraceTypeQuery",
event: "/Script/BlueprintGraph.K2Node_Event",
eWorldPositionIncludedOffsets: "/Script/Engine.EWorldPositionIncludedOffsets",
executionSequence: "/Script/BlueprintGraph.K2Node_ExecutionSequence",
flipflop: "/Engine/EditorBlueprintResources/StandardMacros.StandardMacros:FlipFlop",
forEachElementInEnum: "/Script/BlueprintGraph.K2Node_ForEachElementInEnum",
@@ -151,7 +147,6 @@ export default class Configuration {
isValid: "/Engine/EditorBlueprintResources/StandardMacros.StandardMacros:IsValid",
kismetArrayLibrary: "/Script/Engine.KismetArrayLibrary",
kismetMathLibrary: "/Script/Engine.KismetMathLibrary",
kismetStringLibrary: "/Script/Engine.KismetStringLibrary",
knot: "/Script/BlueprintGraph.K2Node_Knot",
linearColor: "/Script/CoreUObject.LinearColor",
literal: "/Script/BlueprintGraph.K2Node_Literal",
@@ -171,26 +166,20 @@ export default class Configuration {
materialExpressionLogarithm2: "/Script/Engine.MaterialExpressionLogarithm2",
materialExpressionMaterialFunctionCall: "/Script/Engine.MaterialExpressionMaterialFunctionCall",
materialExpressionSquareRoot: "/Script/Engine.MaterialExpressionSquareRoot",
materialExpressionSubtract: "/Script/Engine.MaterialExpressionSubtract",
materialExpressionTextureCoordinate: "/Script/Engine.MaterialExpressionTextureCoordinate",
materialExpressionTextureSample: "/Script/Engine.MaterialExpressionTextureSample",
materialExpressionWorldPosition: "/Script/Engine.MaterialExpressionWorldPosition",
materialGraphNode: "/Script/UnrealEd.MaterialGraphNode",
materialGraphNodeComment: "/Script/UnrealEd.MaterialGraphNode_Comment",
metasoundEditorGraphExternalNode: "/Script/MetasoundEditor.MetasoundEditorGraphExternalNode",
multiGate: "/Script/BlueprintGraph.K2Node_MultiGate",
niagaraBool: "/Script/Niagara.NiagaraBool",
niagaraClipboardContent: "/Script/NiagaraEditor.NiagaraClipboardContent",
niagaraDataInterfaceCollisionQuery: "/Script/Niagara.NiagaraDataInterfaceCollisionQuery",
niagaraDataInterfaceCurlNoise: "/Script/Niagara.NiagaraDataInterfaceCurlNoise",
niagaraDataInterfaceVolumeTexture: "/Script/Niagara.NiagaraDataInterfaceVolumeTexture",
niagaraFloat: "/Script/Niagara.NiagaraFloat",
niagaraInt32: "/Script/Niagara.NiagaraInt32",
niagaraNodeConvert: "/Script/NiagaraEditor.NiagaraNodeConvert",
niagaraMatrix: "/Script/Niagara.NiagaraMatrix",
niagaraNodeFunctionCall: "/Script/NiagaraEditor.NiagaraNodeFunctionCall",
niagaraNodeInput: "/Script/NiagaraEditor.NiagaraNodeInput",
niagaraNodeOp: "/Script/NiagaraEditor.NiagaraNodeOp",
niagaraParameterMap: "/Script/Niagara.NiagaraParameterMap",
niagaraNumeric: "/Script/Niagara.NiagaraNumeric",
niagaraPosition: "/Script/Niagara.NiagaraPosition",
pawn: "/Script/Engine.Pawn",
pcgEditorGraphNode: "/Script/PCGEditor.PCGEditorGraphNode",
@@ -200,7 +189,6 @@ export default class Configuration {
pcgSubgraphSettings: "/Script/PCG.PCGSubgraphSettings",
promotableOperator: "/Script/BlueprintGraph.K2Node_PromotableOperator",
quat4f: "/Script/CoreUObject.Quat4f",
removeDelegate: "/Script/BlueprintGraph.K2Node_RemoveDelegate",
reverseForEachLoop: "/Engine/EditorBlueprintResources/StandardMacros.StandardMacros:ReverseForEachLoop",
rotator: "/Script/CoreUObject.Rotator",
select: "/Script/BlueprintGraph.K2Node_Select",
@@ -220,12 +208,11 @@ export default class Configuration {
variableSet: "/Script/BlueprintGraph.K2Node_VariableSet",
vector: "/Script/CoreUObject.Vector",
vector2D: "/Script/CoreUObject.Vector2D",
vector2f: "/Script/CoreUObject.Vector2f",
vector3f: "/Script/CoreUObject.Vector3f",
vector4f: "/Script/CoreUObject.Vector4f",
whileLoop: "/Engine/EditorBlueprintResources/StandardMacros.StandardMacros:WhileLoop",
}
static pinInputWrapWidth = 145 // px
static pinInputWrapWidth = 143 // px
static removeEventName = "ueb-element-delete"
static scale = {
[-12]: 0.133333,
@@ -250,7 +237,7 @@ export default class Configuration {
7: 2,
}
static smoothScrollTime = 1000 // ms
static stringEscapedCharacters = /["\\]/g // Try to remove
static stringEscapedCharacters = /["\\]/g
static subObjectAttributeNamePrefix = "#SubObject"
/** @param {ObjectEntity} objectEntity */
static subObjectAttributeNameFromEntity = (objectEntity, nameOnly = false) =>
@@ -266,7 +253,7 @@ export default class Configuration {
begin: "ueb-tracking-mouse-begin",
end: "ueb-tracking-mouse-end",
}
static unescapedBackslash = /(?<=(?:[^\\]|^)(?:\\\\)*)\\(?!\\)/ // Try to remove
static unescapedBackslash = /(?<=(?:[^\\]|^)(?:\\\\)*)\\(?!\\)/
static windowApplyEventName = "ueb-window-apply"
static windowApplyButtonText = "OK"
static windowCancelEventName = "ueb-window-cancel"
@@ -278,7 +265,6 @@ export default class Configuration {
"KeepWorld",
"SnapToTarget",
],
[this.paths.eDrawDebugTrace]: ["None", "ForOneFrame", "ForDuration", "Persistent"],
[this.paths.eMaterialSamplerType]: [
"Color",
"Grayscale",
@@ -305,14 +291,6 @@ export default class Configuration {
["NewEnumerator3", "A"],
],
[this.paths.eSamplerSourceMode]: ["From texture asset", "Shared: Wrap", "Shared: Clamp", "Hidden"],
[this.paths.eSearchCase]: ["CaseSensitive", "IgnoreCase"],
[this.paths.eWorldPositionIncludedOffsets]: [
"Absolute World Position (Including Material Shader Offsets)",
"Absolute World Position (Excluding Material Shader Offsets)",
"Camera Relative World Position (Including Material Shader Offsets)",
"Camera Relative World Position (Excluding Material Shader Offsets)",
],
[this.paths.eSearchDir]: ["FromStart", "FromEnd"],
[this.paths.eSpawnActorCollisionHandlingMethod]: [
["Undefined", "Default"],
["AlwaysSpawn", "Always Spawn, Ignore Collisions"],
@@ -320,6 +298,9 @@ export default class Configuration {
["AdjustIfPossibleButDontSpawnIfColliding", "Try To Adjust Location, Don't Spawn If Still Colliding"],
["DontSpawnIfColliding", "Do Not Spawn"],
],
[this.paths.eSearchCase]: ["CaseSensitive", "IgnoreCase"],
[this.paths.eSearchDir]: ["FromStart", "FromEnd"],
[this.paths.eDrawDebugTrace]: ["None", "ForOneFrame", "ForDuration", "Persistent"],
[this.paths.eTextureMipValueMode]: [
"None (use computed mip level)",
"MipLevel (absolute, 0 is full resolution)",

9
js/SVGIcon.js Executable file → Normal file
View File

@@ -67,7 +67,7 @@ export default class SVGIcon {
`
static delegate = html`
<svg width="11" height="11" viewBox="-2 -2 32 32" xmlns="http://www.w3.org/2000/svg">
<svg viewBox="-2 -2 32 32" xmlns="http://www.w3.org/2000/svg">
<rect class="ueb-pin-tofill" fill="black" width="28" height="28" rx="4" stroke="currentColor" stroke-width="5" />
</svg>
`
@@ -388,13 +388,6 @@ export default class SVGIcon {
</svg>
`
static staticPin = html`
<svg width="16" height="12" viewBox="1 0 16 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<path class="ueb-pin-tofill" d="M1 7C1 4 3 1 7 1C10 1 14 3 17 6C18 7 18 7 17 8C14 11 10 13 7 13C3 13 1 10 1 7Z" fill="none" stroke="currentColor" stroke-width="2" />
<path class="ueb-pin-tostroke" d="M 9 4 V 3.5 H 5 V 7 H 9 V 10.5 H 5 V 10" stroke="currentColor" stroke-width="2" />
</svg>
`
static switch = html`
<svg viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect x="3" y="2" width="6" height="2" fill="white" />

0
js/Shortcuts.js Executable file → Normal file
View File

View File

@@ -2,6 +2,31 @@ import Configuration from "./Configuration.js"
export default class Utility {
static booleanConverter = {
fromAttribute: (value, type) => {
value ? "true" : "false"
},
toAttribute: (value, type) => {
if (value === true) {
return "true"
}
if (value === false) {
return "false"
}
return ""
}
}
/** @param {Number} x */
static sigmoid(x, curvature = 1.7) {
return 1 / (1 + (x / (1 - x) ** -curvature))
}
/** @param {Number} x */
static sigmoidPositive(x, curvature = 3.7, length = 1.1) {
return 1 - Math.exp(-((x / length) ** curvature))
}
/** @param {Number} value */
static clamp(value, min = -Infinity, max = Infinity) {
return Math.min(Math.max(value, min), max)
@@ -27,6 +52,23 @@ export default class Utility {
return num.toFixed(decimals)
}
/** @param {String} value */
static numberFromText(value = "") {
value = value.toLowerCase()
switch (value) {
case "zero": return 0
case "one": return 1
case "two": return 2
case "three": return 3
case "four": return 4
case "five": return 5
case "six": return 6
case "seven": return 7
case "eight": return 8
case "nine": return 9
}
}
/**
* @param {Number} num
* @param {Number} decimals
@@ -36,6 +78,16 @@ export default class Utility {
return Math.round(num * power) / power
}
/** @param {Number} num */
static printNumber(num) {
if (num == Number.POSITIVE_INFINITY) {
return "inf"
} else if (num == Number.NEGATIVE_INFINITY) {
return "-inf"
}
return Utility.minDecimals(num)
}
/** @param {Number} num */
static printExponential(num) {
if (num == Number.POSITIVE_INFINITY) {
@@ -80,7 +132,7 @@ export default class Utility {
}
/**
* @param {IEntity} entity
* @param {Attribute} entity
* @param {String} key
* @returns {Boolean}
*/
@@ -147,31 +199,29 @@ export default class Utility {
/**
* @template T
* @param {T[]} reference
* @param {T[]} additional
* @param {(v: T) => void} adding - Process added element
* @param {Array<T>} a
* @param {Array<T>} b
* @param {(l: T, r: T) => Boolean} predicate
* @returns {T[]}
*/
static mergeArrays(reference = [], additional = [], predicate = (l, r) => l == r, adding = v => { }) {
static mergeArrays(a = [], b = [], predicate = (l, r) => l == r) {
let result = []
reference = [...reference]
additional = [...additional]
a = [...a]
b = [...b]
restart:
while (true) {
for (let j = 0; j < additional.length; ++j) {
for (let i = 0; i < reference.length; ++i) {
if (predicate(reference[i], additional[j])) {
for (let j = 0; j < b.length; ++j) {
for (let i = 0; i < a.length; ++i) {
if (predicate(a[i], b[j])) {
// Found an element in common in the two arrays
result.push(
// Take and append all the elements skipped from a
...reference.splice(0, i),
...a.splice(0, i),
// Take and append all the elements skippend from b
...additional.splice(0, j).map(v => (adding(v), v)),
...b.splice(0, j),
// Take and append the element in common
...reference.splice(0, 1)
...a.splice(0, 1)
)
additional.shift() // Remove the same element from b
b.shift() // Remove the same element from b
continue restart
}
}
@@ -179,13 +229,7 @@ export default class Utility {
break restart
}
// Append remaining the elements in the arrays and make it unique
result.push(...reference)
result.push(
...additional
.filter(vb => !result.some(vr => predicate(vr, vb)))
.map((v, k) => (adding(v), v))
)
return result
return [...(new Set(result.concat(...a, ...b)))]
}
/** @param {String} value */
@@ -263,6 +307,11 @@ export default class Utility {
return pathValue.match(regex)?.[1] ?? ""
}
/** @param {LinearColorEntity} value */
static printLinearColor(value) {
return `${Math.round(value.R.valueOf() * 255)}, ${Math.round(value.G.valueOf() * 255)}, ${Math.round(value.B.valueOf() * 255)}`
}
/**
* @param {Number} x
* @param {Number} y

0
js/action/RemoveAllNodes.js Executable file → Normal file
View File

31
js/decoding/nodeColor.js Executable file → Normal file
View File

@@ -8,29 +8,25 @@ export default function nodeColor(entity) {
case Configuration.paths.materialExpressionConstant3Vector:
case Configuration.paths.materialExpressionConstant4Vector:
return Configuration.nodeColors.yellow
case Configuration.paths.materialExpressionFunctionInput:
case Configuration.paths.materialExpressionTextureCoordinate:
case Configuration.paths.materialExpressionWorldPosition:
case Configuration.paths.pcgEditorGraphNodeInput:
case Configuration.paths.pcgEditorGraphNodeOutput:
return Configuration.nodeColors.red
case Configuration.paths.makeStruct:
return Configuration.nodeColors.darkBlue
case Configuration.paths.materialExpressionMaterialFunctionCall:
return Configuration.nodeColors.blue
case Configuration.paths.materialExpressionFunctionInput:
return Configuration.nodeColors.red
case Configuration.paths.materialExpressionTextureSample:
return Configuration.nodeColors.darkTurquoise
case Configuration.paths.niagaraNodeInput:
switch (entity["Usage"]?.toString()) {
case "Attribute": return Configuration.nodeColors.intenseGreen
case "Parameter": return Configuration.nodeColors.red
case "RapidIterationParameter": return Configuration.nodeColors.black
case "SystemConstant": return Configuration.nodeColors.gray
case "TranslatorConstant": return Configuration.nodeColors.gray
default: return Configuration.nodeColors.red
}
case Configuration.paths.materialExpressionTextureCoordinate:
return Configuration.nodeColors.red
case Configuration.paths.pcgEditorGraphNodeInput:
case Configuration.paths.pcgEditorGraphNodeOutput:
return Configuration.nodeColors.red
}
switch (entity.getClass()) {
case Configuration.paths.callFunction:
return entity.bIsPureFunc?.valueOf()
? Configuration.nodeColors.green
: Configuration.nodeColors.blue
case Configuration.paths.niagaraNodeFunctionCall:
return Configuration.nodeColors.darkerBlue
case Configuration.paths.dynamicCast:
@@ -78,11 +74,8 @@ export default function nodeColor(entity) {
return Configuration.nodeColors.intenseGreen
}
}
if (entity.bIsPureFunc?.valueOf() || entity.bDefaultsToPureFunc?.valueOf()) {
if (entity.bIsPureFunc?.valueOf()) {
return Configuration.nodeColors.green
}
if (entity["Input"]?.["Name"]) {
return Configuration.nodeColors.gray
}
return Configuration.nodeColors.blue
}

2
js/decoding/nodeIcon.js Executable file → Normal file
View File

@@ -11,11 +11,9 @@ export default function nodeIcon(entity) {
case Configuration.paths.addDelegate:
case Configuration.paths.asyncAction:
case Configuration.paths.callDelegate:
case Configuration.paths.clearDelegate:
case Configuration.paths.createDelegate:
case Configuration.paths.functionEntry:
case Configuration.paths.functionResult:
case Configuration.paths.removeDelegate:
return SVGIcon.node
case Configuration.paths.customEvent: return SVGIcon.event
case Configuration.paths.doN: return SVGIcon.doN

View File

@@ -1,27 +0,0 @@
import Configuration from "../Configuration.js"
import Utility from "../Utility.js"
import pinTitle from "./pinTitle.js"
/**
* @param {ObjectEntity} entity
* @returns {String?}
*/
export default function nodeSubtitle(entity) {
switch (entity.getType()) {
case Configuration.paths.addDelegate:
case Configuration.paths.clearDelegate:
case Configuration.paths.callDelegate:
case Configuration.paths.removeDelegate:
return null
}
const targetPin = entity
.getPinEntities()
.find(pin => !pin.isHidden() && pin.PinName?.toString() === "self" && pinTitle(pin) === "Target")
if (targetPin) {
const target = entity.FunctionReference?.MemberParent?.getName()
?? targetPin.PinType?.PinSubCategoryObject?.getName()
?? "Untitled"
return target.length > 0 ? `Target is ${Utility.formatStringName(target)}` : null
}
return null
}

116
js/decoding/nodeTemplate.js Executable file → Normal file
View File

@@ -9,88 +9,22 @@ import VariableAccessNodeTemplate from "../template/node/VariableAccessNodeTempl
import VariableConversionNodeTemplate from "../template/node/VariableConversionNodeTemplate.js"
import VariableOperationNodeTemplate from "../template/node/VariableOperationNodeTemplate.js"
const niagaraOperationNodes = [
"Boolean::LogicEq",
"Boolean::LogicNEq",
"Integer::EnumNEq",
"Integer::EnumEq",
...[
"Abs",
"Add",
"ArcCosine(Degrees)",
"ArcCosine(Radians)",
"ArcSine(Degrees)",
"ArcSine(Radians)",
"ArcTangent(Degrees)",
"ArcTangent(Radians)",
"Ceil",
"CmpEQ",
"CmpGE",
"CmpGT",
"CmpLE",
"CmpLT",
"CmpNEQ",
"Cosine(Degrees)",
"Cosine(Radians)",
"DegreesToRadians",
"Div",
"Dot",
"Exp",
"Exp2",
"Floor",
"FMod",
"Frac",
"Length",
"Lerp",
"Log",
"Log2",
"Madd",
"Max",
"Min",
"Mul",
"Negate",
"Normalize",
"OneMinus",
"PI",
"RadiansToDegrees",
"Rcp",
"RcpFast",
"Round",
"RSqrt",
"Sign",
"Sine(Degrees)",
"Sine(Radians)",
"Sqrt",
"Step",
"Subtract",
"Tangent(Degrees)",
"Tangent(Radians)",
"Trunc",
"TWO_PI",
].map(v => "Numeric::" + v),
"Vector3::Cross",
]
const paths = Configuration.paths
/**
* @param {ObjectEntity} nodeEntity
* @return {new () => NodeTemplate}
*/
export default function nodeTemplateClass(nodeEntity) {
const className = nodeEntity.getClass()
if (
className === paths.callFunction
|| className === paths.commutativeAssociativeBinaryOperator
|| className === paths.callArrayFunction
nodeEntity.getClass() === Configuration.paths.callFunction
|| nodeEntity.getClass() === Configuration.paths.commutativeAssociativeBinaryOperator
|| nodeEntity.getClass() === Configuration.paths.callArrayFunction
) {
const memberParent = nodeEntity.FunctionReference?.MemberParent?.path ?? ""
const memberName = nodeEntity.FunctionReference?.MemberName?.toString()
const memberName = nodeEntity.FunctionReference?.MemberName?.valueOf()
if (
memberName && (
memberParent === paths.kismetMathLibrary
|| memberParent === paths.kismetArrayLibrary
|| memberParent === paths.kismetStringLibrary
memberParent === Configuration.paths.kismetMathLibrary
|| memberParent === Configuration.paths.kismetArrayLibrary
)) {
if (memberName.startsWith("Conv_")) {
return VariableConversionNodeTemplate
@@ -143,37 +77,45 @@ export default function nodeTemplateClass(nodeEntity) {
return VariableOperationNodeTemplate
}
}
if (memberParent === paths.blueprintSetLibrary) {
if (memberParent === Configuration.paths.blueprintSetLibrary) {
return VariableOperationNodeTemplate
}
if (memberParent === paths.blueprintMapLibrary) {
if (memberParent === Configuration.paths.blueprintMapLibrary) {
return VariableOperationNodeTemplate
}
}
switch (className) {
case paths.comment:
case paths.materialGraphNodeComment:
switch (nodeEntity.getClass()) {
case Configuration.paths.comment:
case Configuration.paths.materialGraphNodeComment:
return CommentNodeTemplate
case paths.createDelegate:
case Configuration.paths.createDelegate:
return NodeTemplate
case paths.metasoundEditorGraphExternalNode:
case Configuration.paths.metasoundEditorGraphExternalNode:
if (nodeEntity["ClassName"]?.["Name"] == "Add") {
return MetasoundOperationTemplate
}
return MetasoundNodeTemplate
case paths.niagaraNodeOp:
if (niagaraOperationNodes.includes(nodeEntity.OpName?.toString())) {
case Configuration.paths.niagaraNodeOp:
if (
[
"Boolean::LogicEq",
"Boolean::LogicNEq",
"Numeric::Abs",
"Numeric::Add",
"Numeric::Mul",
].includes(nodeEntity.OpName?.valueOf())
) {
return VariableOperationNodeTemplate
}
break
case paths.promotableOperator:
case Configuration.paths.promotableOperator:
return VariableOperationNodeTemplate
case paths.knot:
case Configuration.paths.knot:
return KnotNodeTemplate
case paths.literal:
case paths.self:
case paths.variableGet:
case paths.variableSet:
case Configuration.paths.literal:
case Configuration.paths.self:
case Configuration.paths.variableGet:
case Configuration.paths.variableSet:
return VariableAccessNodeTemplate
}
if (nodeEntity.isEvent()) {

293
js/decoding/nodeTitle.js Executable file → Normal file
View File

@@ -5,7 +5,6 @@ import LinearColorEntity from "../entity/LinearColorEntity.js"
import MirroredEntity from "../entity/MirroredEntity.js"
import VectorEntity from "../entity/VectorEntity.js"
const paths = Configuration.paths
const sequencerScriptingNameRegex = /\/Script\/SequencerScripting\.MovieSceneScripting(.+)Channel/
const keyNameValue = {
"A_AccentGrave": "à",
@@ -35,83 +34,6 @@ const keyNameValue = {
"Subtract": "Num -",
"Tilde": "`",
}
const niagaraNodeNames = {
"Boolean::LogicAnd": "Logic AND",
"Boolean::LogicEq": "==",
"Boolean::LogicNEq": "!=",
"Boolean::LogicNot": "Logic NOT",
"Boolean::LogicOr": "Logic OR",
"Integer::BitAnd": "Bitwise AND",
"Integer::BitLShift": "Bitwise Left Shift",
"Integer::BitNot": "Bitwise NOT",
"Integer::BitOr": "Bitwise OR",
"Integer::BitRShift": "Bitwise Right Shift",
"Integer::BitXOr": "Bitwise XOR",
"Integer::EnumEq": "==",
"Integer::EnumNEq": "!=",
"Matrix::MatrixMultiply": "Multiply (Matrix * Matrix)",
"Matrix::MatrixVectorMultiply": "Multiply (Matrix * Vector4)",
// Numeric::
...Object.fromEntries(Object.entries({
"Add": "+",
"ArcCosine": "ArcCosine",
"ArcCosine(Degrees)": "ArcCos(D)",
"ArcCosine(Radians)": "ArcCos(R)",
"ArcSine": "ArcSine",
"ArcSine(Degrees)": "ArcSin(D)",
"ArcSine(Radians)": "ArcSin(R)",
"ArcTangent(Degrees)": "ArcTan(D)",
"ArcTangent(Radians)": "ArcTan(R)",
"CmpEQ": "==",
"CmpGE": ">=",
"CmpGT": ">",
"CmpLE": "<=",
"CmpLT": "<",
"CmpNEQ": "!=",
"Cosine(Degrees)": "Cos(D)",
"Cosine(Radians)": "Cos(R)",
"DegreesToRadians": "DegToRad",
"DistancePos": "Distance",
"Div": String.fromCharCode(0x00f7),
"FMod": "%",
"FModFast": "Modulo Fast",
"Length": "Len",
"Madd": `(A${String.fromCharCode(0x2a2f)}B)+C`,
"Mul": String.fromCharCode(0x2a2f),
"Negate": "-A",
"OneMinus": "1-A",
"PI": String.fromCharCode(0x03C0),
"RadiansToDegrees": "RadToDeg",
"Rand Float": "Random Float",
"Rand Integer": "Random Integer",
"Rand": "Random",
"Rcp": "Reciprocal",
"RSqrt": "Rcp Sqrt",
"Sine(Degrees)": "Sin(D)",
"Sine(Radians)": "Sin(R)",
"Subtract": "-",
"Tangent(Degrees)": "Tan(D)",
"Tangent(Radians)": "Tan(R)",
"TWO_PI": `2 ${String.fromCharCode(0x03C0)}`,
}).map(([k, v]) => ["Numeric::" + k, v])),
}
/** @param {String} value */
function numberFromText(value = "") {
value = value.toLowerCase()
switch (value) {
case "zero": return 0
case "one": return 1
case "two": return 2
case "three": return 3
case "four": return 4
case "five": return 5
case "six": return 6
case "seven": return 7
case "eight": return 8
case "nine": return 9
}
}
function keyName(value) {
/** @type {String} */
@@ -119,134 +41,114 @@ function keyName(value) {
if (result) {
return result
}
result = numberFromText(value)?.toString()
result = Utility.numberFromText(value)?.toString()
if (result) {
return result
}
const match = value.match(/NumPad([a-zA-Z]+)/)
if (match) {
result = numberFromText(match[1]).toString()
result = Utility.numberFromText(match[1]).toString()
if (result) {
return "Num " + result
}
}
}
/**
* @param {ObjectEntity} entity
* @returns {String}
*/
/** @param {ObjectEntity} entity */
export default function nodeTitle(entity) {
let value
let input
switch (entity.getType()) {
case paths.addDelegate:
value ??= "Bind Event to "
case paths.clearDelegate:
value ??= "Unbind all Events from "
case paths.removeDelegate:
value ??= "Unbind Event from "
return value + Utility.formatStringName(
entity.DelegateReference?.MemberName?.toString().replace(/Delegate$/, "") ?? "None"
)
case paths.asyncAction:
case Configuration.paths.asyncAction:
if (entity.ProxyFactoryFunctionName) {
return Utility.formatStringName(entity.ProxyFactoryFunctionName?.toString())
return Utility.formatStringName(entity.ProxyFactoryFunctionName?.valueOf())
}
case paths.actorBoundEvent:
case paths.componentBoundEvent:
return `${Utility.formatStringName(entity.DelegatePropertyName?.toString())} (${entity.ComponentPropertyName?.toString() ?? "Unknown"})`
case paths.callDelegate:
return `Call ${entity.DelegateReference?.MemberName?.toString() ?? "None"}`
case paths.createDelegate:
case Configuration.paths.actorBoundEvent:
case Configuration.paths.componentBoundEvent:
return `${Utility.formatStringName(entity.DelegatePropertyName?.valueOf())} (${entity.ComponentPropertyName?.valueOf() ?? "Unknown"})`
case Configuration.paths.callDelegate:
return `Call ${entity.DelegateReference?.MemberName?.valueOf() ?? "None"}`
case Configuration.paths.createDelegate:
return "Create Event"
case paths.customEvent:
case Configuration.paths.customEvent:
if (entity.CustomFunctionName) {
return entity.CustomFunctionName?.toString()
return entity.CustomFunctionName?.valueOf()
}
case paths.dynamicCast:
case Configuration.paths.dynamicCast:
if (!entity.TargetType) {
return "Bad cast node" // Target type not found
}
return `Cast To ${entity.TargetType?.getName()}`
case paths.enumLiteral:
case Configuration.paths.enumLiteral:
return `Literal enum ${entity.Enum?.getName()}`
case paths.event:
return `Event ${(entity.EventReference?.MemberName?.toString() ?? "").replace(/^Receive/, "")}`
case paths.executionSequence:
case Configuration.paths.event:
return `Event ${(entity.EventReference?.MemberName?.valueOf() ?? "").replace(/^Receive/, "")}`
case Configuration.paths.executionSequence:
return "Sequence"
case paths.forEachElementInEnum:
case Configuration.paths.forEachElementInEnum:
return `For Each ${entity.Enum?.getName()}`
case paths.forEachLoopWithBreak:
case Configuration.paths.forEachLoopWithBreak:
return "For Each Loop with Break"
case paths.functionEntry:
return entity.FunctionReference?.MemberName?.toString() === "UserConstructionScript"
case Configuration.paths.functionEntry:
return entity.FunctionReference?.MemberName?.valueOf() === "UserConstructionScript"
? "Construction Script"
: entity.FunctionReference?.MemberName?.toString()
case paths.functionResult:
: entity.FunctionReference?.MemberName?.valueOf()
case Configuration.paths.functionResult:
return "Return Node"
case paths.ifThenElse:
case Configuration.paths.ifThenElse:
return "Branch"
case paths.makeStruct:
case Configuration.paths.makeStruct:
if (entity.StructType) {
return `Make ${entity.StructType.getName()}`
}
case paths.materialExpressionComponentMask: {
case Configuration.paths.materialExpressionComponentMask: {
const materialObject = entity.getMaterialSubobject()
if (materialObject) {
return `Mask ( ${Configuration.rgba
.filter(k => /** @type {MirroredEntity<typeof BooleanEntity>} */(materialObject[k]).getter().value === true)
.map(v => v + " ")
.join("")})`
}
return `Mask ( ${Configuration.rgba
.filter(k => /** @type {MirroredEntity<typeof BooleanEntity>} */(materialObject[k]).getter().value === true)
.map(v => v + " ")
.join("")})`
}
case paths.materialExpressionConstant:
value ??= [entity.getCustomproperties().find(pinEntity => pinEntity.PinName.toString() == "Value")?.DefaultValue]
case paths.materialExpressionConstant2Vector:
value ??= [
entity.getCustomproperties().find(pinEntity => pinEntity.PinName?.toString() == "X")?.DefaultValue,
entity.getCustomproperties().find(pinEntity => pinEntity.PinName?.toString() == "Y")?.DefaultValue,
case Configuration.paths.materialExpressionConstant:
input ??= [entity.getCustomproperties().find(pinEntity => pinEntity.PinName.valueOf() == "Value")?.DefaultValue]
case Configuration.paths.materialExpressionConstant2Vector:
input ??= [
entity.getCustomproperties().find(pinEntity => pinEntity.PinName?.valueOf() == "X")?.DefaultValue,
entity.getCustomproperties().find(pinEntity => pinEntity.PinName?.valueOf() == "Y")?.DefaultValue,
]
case paths.materialExpressionConstant3Vector:
case paths.materialExpressionConstant4Vector:
if (!value) {
case Configuration.paths.materialExpressionConstant3Vector:
case Configuration.paths.materialExpressionConstant4Vector:
if (!input) {
const vector = entity.getCustomproperties()
.find(pinEntity => pinEntity.PinName?.toString() == "Constant")
.find(pinEntity => pinEntity.PinName?.valueOf() == "Constant")
?.DefaultValue
value = vector instanceof VectorEntity ? [vector.X, vector.Y, vector.Z].map(v => v.valueOf())
input = vector instanceof VectorEntity ? [vector.X, vector.Y, vector.Z].map(v => v.valueOf())
: vector instanceof LinearColorEntity ? [vector.R, vector.G, vector.B, vector.A].map(v => v.valueOf())
: /** @type {Number[]} */([])
}
if (value?.length > 0) {
return value.map(v => Utility.printExponential(v)).join(",")
if (input.length > 0) {
return input.map(v => Utility.printExponential(v)).join(",")
}
value = undefined
break
case paths.materialExpressionFunctionInput: {
case Configuration.paths.materialExpressionFunctionInput: {
const materialObject = entity.getMaterialSubobject()
const inputName = materialObject?.InputName ?? "In"
const inputType = materialObject?.InputType?.value.match(/^.+?_(\w+)$/)?.[1] ?? "Vector3"
return `Input ${inputName} (${inputType})`
}
case paths.materialExpressionLogarithm:
case Configuration.paths.materialExpressionLogarithm:
return "Ln"
case paths.materialExpressionLogarithm10:
case Configuration.paths.materialExpressionLogarithm10:
return "Log10"
case paths.materialExpressionLogarithm2:
case Configuration.paths.materialExpressionLogarithm2:
return "Log2"
case paths.materialExpressionMaterialFunctionCall:
case Configuration.paths.materialExpressionMaterialFunctionCall:
const materialFunction = entity.getMaterialSubobject()?.MaterialFunction
if (materialFunction) {
return materialFunction.getName()
}
break
case paths.materialExpressionSquareRoot:
case Configuration.paths.materialExpressionSquareRoot:
return "Sqrt"
case paths.materialExpressionSubtract:
const materialObject = entity.getMaterialSubobject()
if (materialObject) {
return `Subtract(${materialObject.ConstA ?? "1"},${materialObject.ConstB ?? "1"})`
}
case paths.metasoundEditorGraphExternalNode: {
case Configuration.paths.metasoundEditorGraphExternalNode: {
const name = entity["ClassName"]?.["Name"]
if (name) {
switch (name) {
@@ -255,21 +157,13 @@ export default function nodeTitle(entity) {
}
}
}
case paths.niagaraNodeConvert:
/** @type {String} */
const targetType = (entity["AutowireMakeType"]?.["ClassStructOrEnum"] ?? "")
.toString()
.match(/(?:Niagara)?(\w+)['"]*$/)
?.[1]
?? ""
return `Make ${targetType}`
case paths.pcgEditorGraphNodeInput:
case Configuration.paths.pcgEditorGraphNodeInput:
return "Input"
case paths.pcgEditorGraphNodeOutput:
case Configuration.paths.pcgEditorGraphNodeOutput:
return "Output"
case paths.spawnActorFromClass:
case Configuration.paths.spawnActorFromClass:
let className = entity.getCustomproperties()
.find(pinEntity => pinEntity.PinName.toString() == "ReturnValue")
.find(pinEntity => pinEntity.PinName.valueOf() == "ReturnValue")
?.PinType
?.PinSubCategoryObject
?.getName()
@@ -277,16 +171,15 @@ export default function nodeTitle(entity) {
className = null
}
return `SpawnActor ${Utility.formatStringName(className ?? "NONE")}`
case paths.switchEnum:
case Configuration.paths.switchEnum:
return `Switch on ${entity.Enum?.getName() ?? "Enum"}`
case paths.switchInteger:
case Configuration.paths.switchInteger:
return `Switch on Int`
case paths.variableGet:
case Configuration.paths.variableGet:
return ""
case paths.variableSet:
case Configuration.paths.variableSet:
return "SET"
}
const className = entity.getClass()
let switchTarget = entity.switchTarget()
if (switchTarget) {
if (switchTarget[0] !== "E") {
@@ -295,31 +188,30 @@ export default function nodeTitle(entity) {
return `Switch on ${switchTarget}`
}
if (entity.isComment()) {
return entity.NodeComment.toString()
return entity.NodeComment
}
const keyNameSymbol = entity.getHIDAttribute()
if (keyNameSymbol) {
const name = keyNameSymbol.toString()
let title = keyName(name) ?? Utility.formatStringName(name)
if (className === paths.inputDebugKey) {
if (entity.getClass() === Configuration.paths.inputDebugKey) {
title = "Debug Key " + title
} else if (className === paths.getInputAxisKeyValue) {
} else if (entity.getClass() === Configuration.paths.getInputAxisKeyValue) {
title = "Get " + title
}
return title
}
if (className === paths.macro) {
if (entity.getClass() === Configuration.paths.macro) {
return Utility.formatStringName(entity.MacroGraphReference?.getMacroName())
}
const materialSubobject = entity.getMaterialSubobject()
if (materialSubobject) {
let result = nodeTitle(materialSubobject)
if (entity.isMaterial() && entity.getMaterialSubobject()) {
let result = nodeTitle(entity.getMaterialSubobject())
result = result.match(/Material Expression (.+)/)?.[1] ?? result
return result
}
if (entity.isPcg() && entity.getPcgSubobject()) {
let pcgSubobject = entity.getPcgSubobject()
let result = pcgSubobject.NodeTitle ? pcgSubobject.NodeTitle.toString() : nodeTitle(pcgSubobject)
let result = pcgSubobject.NodeTitle ? pcgSubobject.NodeTitle : nodeTitle(pcgSubobject)
return result
}
const subgraphObject = entity.getSubgraphObject()
@@ -328,7 +220,7 @@ export default function nodeTitle(entity) {
}
const settingsObject = entity.getSettingsObject()
if (settingsObject) {
if (settingsObject.ExportPath?.valueOf()?.type === paths.pcgHiGenGridSizeSettings) {
if (settingsObject.ExportPath.type === Configuration.paths.pcgHiGenGridSizeSettings) {
return `Grid Size: ${(
settingsObject.HiGenGridSize?.toString().match(/\d+/)?.[0]?.concat("00")
?? settingsObject.HiGenGridSize?.toString().match(/^\w+$/)?.[0]
@@ -338,7 +230,7 @@ export default function nodeTitle(entity) {
return Utility.formatStringName(settingsObject.BlueprintElementType.getName())
}
if (settingsObject.Operation) {
const match = settingsObject.Name?.toString().match(/PCGMetadata(\w+)Settings_\d+/)
const match = settingsObject.Name?.valueOf().match(/PCGMetadata(\w+)Settings_\d+/)
if (match) {
return Utility.formatStringName(match[1] + ": " + settingsObject.Operation)
}
@@ -348,9 +240,9 @@ export default function nodeTitle(entity) {
return settingsSubgraphObject.Graph.getName()
}
}
let memberName = entity.FunctionReference?.MemberName?.toString()
let memberName = entity.FunctionReference?.MemberName?.valueOf()
if (memberName) {
const memberParent = entity.FunctionReference.MemberParent?.path ?? ""
const memberParent = entity.FunctionReference.MemberParent?.path?.valueOf() ?? ""
switch (memberName) {
case "AddKey":
let result = memberParent.match(sequencerScriptingNameRegex)
@@ -370,11 +262,10 @@ export default function nodeTitle(entity) {
)
}
switch (memberParent) {
case paths.blueprintGameplayTagLibrary:
case paths.kismetMathLibrary:
case paths.kismetStringLibrary:
case paths.slateBlueprintLibrary:
case paths.timeManagementBlueprintLibrary:
case Configuration.paths.blueprintGameplayTagLibrary:
case Configuration.paths.kismetMathLibrary:
case Configuration.paths.slateBlueprintLibrary:
case Configuration.paths.timeManagementBlueprintLibrary:
const leadingLetter = memberName.match(/[BF]([A-Z]\w+)/)
if (leadingLetter) {
// Some functions start with B or F (Like FCeil, FMax, BMin)
@@ -458,7 +349,7 @@ export default function nodeTitle(entity) {
return "^"
}
break
case paths.blueprintSetLibrary:
case Configuration.paths.blueprintSetLibrary:
{
const setOperationMatch = memberName.match(/Set_(\w+)/)
if (setOperationMatch) {
@@ -466,7 +357,7 @@ export default function nodeTitle(entity) {
}
}
break
case paths.blueprintMapLibrary:
case Configuration.paths.blueprintMapLibrary:
{
const setOperationMatch = memberName.match(/Map_(\w+)/)
if (setOperationMatch) {
@@ -474,7 +365,7 @@ export default function nodeTitle(entity) {
}
}
break
case paths.kismetArrayLibrary:
case Configuration.paths.kismetArrayLibrary:
{
const arrayOperationMath = memberName.match(/Array_(\w+)/)
if (arrayOperationMath) {
@@ -486,24 +377,26 @@ export default function nodeTitle(entity) {
return Utility.formatStringName(memberName)
}
if (entity.OpName) {
return niagaraNodeNames[entity.OpName.toString()]
?? Utility.formatStringName(entity.OpName.toString().replaceAll(/(?:^\w+(?<!^Matrix))?::/g, " "))
switch (entity.OpName.valueOf()) {
case "Boolean::LogicAnd": return "Logic AND"
case "Boolean::LogicEq": return "=="
case "Boolean::LogicNEq": return "!="
case "Boolean::LogicNot": return "Logic NOT"
case "Boolean::LogicOr": return "Logic OR"
case "Matrix::MatrixMultiply": return "Multiply (Matrix * Matrix)"
case "Matrix::MatrixVectorMultiply": return "Multiply (Matrix * Vector4)"
case "Numeric::Abs": return "Abs"
case "Numeric::Add": return "+"
case "Numeric::DistancePos": return "Distance"
case "Numeric::Mul": return String.fromCharCode(0x2a2f)
}
return Utility.formatStringName(entity.OpName.valueOf()).replaceAll("::", " ")
}
if (entity.FunctionDisplayName) {
return Utility.formatStringName(entity.FunctionDisplayName.toString())
return Utility.formatStringName(entity.FunctionDisplayName.valueOf())
}
if (entity.ObjectRef) {
return entity.ObjectRef.getName()
}
let prefix
if (
className.startsWith(prefix = "/Script/NiagaraEditor.NiagaraNodeParameter")
|| className.startsWith(prefix = "/Script/NiagaraEditor.NiagaraNode")
) {
return entity["Input"]?.["Name"]?.toString() ?? Utility.formatStringName(className.substring(prefix.length))
}
if (entity.ParameterName) {
return entity.ParameterName.toString()
}
return Utility.formatStringName(entity.getNameAndCounter()[0])
}

22
js/decoding/nodeVariadic.js Executable file → Normal file
View File

@@ -7,7 +7,7 @@ import StringEntity from "../entity/StringEntity.js"
/** @param {PinEntity} pinEntity */
const indexFromUpperCaseLetterName = pinEntity =>
pinEntity.PinName?.toString().match(/^\s*([A-Z])\s*$/)?.[1]?.charCodeAt(0) - "A".charCodeAt(0)
pinEntity.PinName?.valueOf().match(/^\s*([A-Z])\s*$/)?.[1]?.charCodeAt(0) - "A".charCodeAt(0)
/** @param {ObjectEntity} entity */
export default function nodeVariadic(entity) {
@@ -18,12 +18,11 @@ export default function nodeVariadic(entity) {
/** @type {(newPinIndex: Number, minIndex: Number, maxIndex: Number, newPin: PinEntity) => String} */
let pinNameFromIndex
const type = entity.getType()
let prefix
let name
switch (type) {
case Configuration.paths.commutativeAssociativeBinaryOperator:
case Configuration.paths.promotableOperator:
name = entity.FunctionReference?.MemberName?.toString()
name = entity.FunctionReference?.MemberName?.valueOf()
switch (name) {
default:
if (
@@ -60,16 +59,11 @@ export default function nodeVariadic(entity) {
break
}
break
case Configuration.paths.executionSequence:
prefix ??= "Then"
case Configuration.paths.multiGate:
prefix ??= "Out"
pinEntities ??= () => entity.getPinEntities().filter(pinEntity => pinEntity.isOutput())
pinIndexFromEntity ??= pinEntity => Number(
pinEntity.PinName?.toString().match(new RegExp(String.raw`^\s*${prefix}[_\s]+(\d+)\s*$`, "i"))?.[1]
)
pinIndexFromEntity ??= pinEntity => Number(pinEntity.PinName?.valueOf().match(/^\s*Out[_\s]+(\d+)\s*$/i)?.[1])
pinNameFromIndex ??= (index, min = -1, max = -1, newPin) =>
`${prefix} ${index >= 0 ? index : min > 0 ? `${prefix} 0` : max + 1}`
`Out ${index >= 0 ? index : min > 0 ? "Out 0" : max + 1}`
break
// case Configuration.paths.niagaraNodeOp:
// pinEntities ??= () => entity.getPinEntities().filter(pinEntity => pinEntity.isInput())
@@ -83,7 +77,7 @@ export default function nodeVariadic(entity) {
// break
case Configuration.paths.switchInteger:
pinEntities ??= () => entity.getPinEntities().filter(pinEntity => pinEntity.isOutput())
pinIndexFromEntity ??= pinEntity => Number(pinEntity.PinName?.toString().match(/^\s*(\d+)\s*$/)?.[1])
pinIndexFromEntity ??= pinEntity => Number(pinEntity.PinName?.valueOf().match(/^\s*(\d+)\s*$/)?.[1])
pinNameFromIndex ??= (index, min = -1, max = -1, newPin) => (index < 0 ? max + 1 : index).toString()
break
case Configuration.paths.switchGameplayTag:
@@ -98,7 +92,7 @@ export default function nodeVariadic(entity) {
case Configuration.paths.switchName:
case Configuration.paths.switchString:
pinEntities ??= () => entity.getPinEntities().filter(pinEntity => pinEntity.isOutput())
pinIndexFromEntity ??= pinEntity => Number(pinEntity.PinName.toString().match(/^\s*Case[_\s]+(\d+)\s*$/i)?.[1])
pinIndexFromEntity ??= pinEntity => Number(pinEntity.PinName.valueOf().match(/^\s*Case[_\s]+(\d+)\s*$/i)?.[1])
pinNameFromIndex ??= (index, min = -1, max = -1, newPin) => {
const result = `Case_${index >= 0 ? index : min > 0 ? "0" : max + 1}`
entity.PinNames ??= new ArrayEntity()
@@ -150,10 +144,6 @@ export default function nodeVariadic(entity) {
newPin.PinId = new GuidEntity()
newPin.PinName = new StringEntity(pinNameFromIndex(index, min, max, newPin))
newPin.PinToolTip = undefined
if (newPin.DefaultValue) {
// @ts-expect-error
newPin.DefaultValue = new (newPin.DefaultValue.constructor)()
}
entity.getCustomproperties(true).push(newPin)
return newPin
}

58
js/decoding/pinColor.js Executable file → Normal file
View File

@@ -2,12 +2,24 @@ import { css } from "lit"
import Configuration from "../Configuration.js"
const colors = {
[Configuration.paths.niagaraBool]: css`146, 0, 0`,
[Configuration.paths.niagaraDataInterfaceVolumeTexture]: css`0, 168, 242`,
[Configuration.paths.niagaraFloat]: css`160, 250, 68`,
[Configuration.paths.niagaraMatrix]: css`0, 88, 200`,
[Configuration.paths.niagaraNumeric]: css`0, 88, 200`,
[Configuration.paths.niagaraPosition]: css`251, 146, 251`,
[Configuration.paths.quat4f]: css`0, 88, 200`,
[Configuration.paths.rotator]: css`157, 177, 251`,
[Configuration.paths.transform]: css`227, 103, 0`,
[Configuration.paths.vector]: css`251, 198, 34`,
[Configuration.paths.vector3f]: css`250, 200, 36`,
[Configuration.paths.vector4f]: css`0, 88, 200`,
"Any": css`132, 132, 132`,
"Any[]": css`132, 132, 132`,
"audio": css`252, 148, 252`,
"blue": css`0, 0, 255`,
"bool": css`146, 0, 0`,
"byte": css`0, 110, 100`,
"byte": css`0, 109, 99`,
"class": css`88, 0, 186`,
"default": css`255, 255, 255`,
"delegate": css`255, 56, 56`,
@@ -15,16 +27,16 @@ const colors = {
"exec": css`240, 240, 240`,
"float": css`160, 252, 70`,
"green": css`0, 255, 0`,
"int": css`30, 224, 172`,
"int": css`31, 224, 172`,
"int32": css`30, 224, 172`,
"int64": css`170, 224, 172`,
"int64": css`169, 223, 172`,
"interface": css`238, 252, 168`,
"name": css`200, 128, 252`,
"name": css`201, 128, 251`,
"object": css`0, 168, 242`,
"Param": css`255, 166, 40`,
"Param[]": css`255, 166, 40`,
"Point": css`64, 138, 255`,
"Point[]": css`64, 137, 255`,
"Param": css`255, 166, 39`,
"Param[]": css`255, 166, 39`,
"Point": css`63, 137, 255`,
"Point[]": css`63, 137, 255`,
"real": css`54, 208, 0`,
"red": css`255, 0, 0`,
"string": css`251, 0, 208`,
@@ -36,37 +48,21 @@ const colors = {
"Volume": css`230, 69, 188`,
"Volume[]": css`230, 69, 188`,
"wildcard": css`128, 120, 120`,
[Configuration.paths.linearColor]: css`0, 88, 200`,
[Configuration.paths.niagaraBool]: css`146, 0, 0`,
[Configuration.paths.niagaraDataInterfaceCollisionQuery]: css`0, 168, 242`,
[Configuration.paths.niagaraDataInterfaceCurlNoise]: css`0, 168, 242`,
[Configuration.paths.niagaraDataInterfaceVolumeTexture]: css`0, 168, 242`,
[Configuration.paths.niagaraFloat]: css`160, 250, 68`,
[Configuration.paths.niagaraInt32]: css`30, 224, 172`,
[Configuration.paths.niagaraPosition]: css`251, 146, 251`,
[Configuration.paths.quat4f]: css`0, 88, 200`,
[Configuration.paths.rotator]: css`157, 177, 251`,
[Configuration.paths.transform]: css`227, 103, 0`,
[Configuration.paths.vector]: css`251, 198, 34`,
[Configuration.paths.vector2f]: css`0, 88, 200`,
[Configuration.paths.vector3f]: css`250, 200, 36`,
[Configuration.paths.vector4f]: css`0, 88, 200`,
}
const pinColorMaterial = css`120, 120, 120`
/** @param {PinEntity<IEntity>} entity */
/** @param {PinEntity} entity */
export default function pinColor(entity) {
if (entity.PinType.PinCategory?.toString() === "mask") {
const result = colors[entity.PinType.PinSubCategory?.toString()]
if (entity.PinType.PinCategory?.valueOf() == "mask") {
const result = colors[entity.PinType.PinSubCategory]
if (result) {
return result
}
} else if (entity.PinType.PinCategory?.toString() === "optional") {
} else if (entity.PinType.PinCategory?.valueOf() == "optional") {
return pinColorMaterial
}
const type = entity.getType()
return colors[type]
?? colors[entity.PinType.PinCategory?.toString().toLowerCase()]
?? (type.startsWith("/Script/Niagara.") ? colors["struct"] : colors["default"])
return colors[entity.getType()]
?? colors[entity.PinType.PinCategory?.valueOf().toLowerCase()]
?? colors["default"]
}

38
js/decoding/pinTemplate.js Executable file → Normal file
View File

@@ -7,7 +7,6 @@ import IntPinTemplate from "../template/pin/IntPinTemplate.js"
import LinearColorPinTemplate from "../template/pin/LinearColorPinTemplate.js"
import NamePinTemplate from "../template/pin/NamePinTemplate.js"
import PinTemplate from "../template/pin/PinTemplate.js"
import ReadonlyNamePinTemplate from "../template/pin/ReadonlyInputPinTemplate.js"
import RealPinTemplate from "../template/pin/RealPinTemplate.js"
import ReferencePinTemplate from "../template/pin/ReferencePinTemplate.js"
import RotatorPinTemplate from "../template/pin/RotatorPinTemplate.js"
@@ -15,47 +14,38 @@ import StringPinTemplate from "../template/pin/StringPinTemplate.js"
import Vector2DPinTemplate from "../template/pin/Vector2DPinTemplate.js"
import Vector4DPinTemplate from "../template/pin/Vector4DPinTemplate.js"
import VectorPinTemplate from "../template/pin/VectorPinTemplate.js"
import pinTitle from "./pinTitle.js"
const inputPinTemplates = {
"bool": BoolPinTemplate,
"byte": IntPinTemplate,
"enum": EnumPinTemplate,
"float": RealPinTemplate,
"int": IntPinTemplate,
"int64": Int64PinTemplate,
"MUTABLE_REFERENCE": ReferencePinTemplate,
"name": NamePinTemplate,
"real": RealPinTemplate,
"rg": Vector2DPinTemplate,
"string": StringPinTemplate,
[Configuration.paths.linearColor]: LinearColorPinTemplate,
[Configuration.paths.niagaraBool]: BoolPinTemplate,
[Configuration.paths.niagaraFloat]: RealPinTemplate,
[Configuration.paths.niagaraInt32]: IntPinTemplate,
[Configuration.paths.niagaraPosition]: VectorPinTemplate,
[Configuration.paths.rotator]: RotatorPinTemplate,
[Configuration.paths.vector]: VectorPinTemplate,
[Configuration.paths.vector2D]: Vector2DPinTemplate,
[Configuration.paths.vector2f]: Vector2DPinTemplate,
[Configuration.paths.vector3f]: VectorPinTemplate,
[Configuration.paths.vector4f]: Vector4DPinTemplate,
"bool": BoolPinTemplate,
"byte": IntPinTemplate,
"enum": EnumPinTemplate,
"int": IntPinTemplate,
"int64": Int64PinTemplate,
"MUTABLE_REFERENCE": ReferencePinTemplate,
"name": NamePinTemplate,
"rg": Vector2DPinTemplate,
"real": RealPinTemplate,
"string": StringPinTemplate,
}
/** @param {PinEntity<IEntity>} entity */
/** @param {PinEntity} entity */
export default function pinTemplate(entity) {
if (entity.PinType.ContainerType?.toString() === "Array") {
if (entity.PinType.ContainerType?.valueOf() === "Array") {
return PinTemplate
}
if (entity.PinType.bIsReference?.valueOf() && !entity.PinType.bIsConst?.valueOf()) {
return inputPinTemplates["MUTABLE_REFERENCE"]
}
if (entity.isExecution()) {
if (entity.getType() === "exec") {
return ExecPinTemplate
}
if (entity.PinName?.toString() === "self" && pinTitle(entity) === "Target") {
return ReadonlyNamePinTemplate
}
const type = entity.getType()
return (entity.isInput() ? inputPinTemplates[type] : PinTemplate) ?? PinTemplate
return (entity.isInput() ? inputPinTemplates[entity.getType()] : PinTemplate) ?? PinTemplate
}

11
js/decoding/pinTitle.js Executable file → Normal file
View File

@@ -1,16 +1,19 @@
import Utility from "../Utility.js"
/** @param {PinEntity<IEntity>} entity */
/** @param {PinEntity} entity */
export default function pinTitle(entity) {
let result = entity.PinFriendlyName
? entity.PinFriendlyName.toString()
: Utility.formatStringName(entity.PinName?.toString() ?? "")
: Utility.formatStringName(entity.PinName?.valueOf() ?? "")
let match
if (match = entity.PinToolTip?.toString().match(/\s*(.+?(?=\n)|.+\S)\s*/)) {
if (
entity.PinToolTip
// Match up until the first \n excluded or last character
&& (match = entity.PinToolTip?.valueOf().match(/\s*(.+?(?=\n)|.+\S)\s*/))
) {
if (match[1].toLowerCase() === result.toLowerCase()) {
return match[1] // In case they match, then keep the case of the PinToolTip
}
}
result = result.replace(/^Module\./, "")
return result
}

0
js/element/ColorHandlerElement.js Executable file → Normal file
View File

0
js/element/ColorSliderElement.js Executable file → Normal file
View File

0
js/element/DropdownElement.js Executable file → Normal file
View File

4
js/element/ElementFactory.js Executable file → Normal file
View File

@@ -1,11 +1,11 @@
export default class ElementFactory {
/** @type {Map<String, IElementConstructor>} */
/** @type {Map<String, AnyConstructor<IElement>>} */
static #elementConstructors = new Map()
/**
* @param {String} tagName
* @param {IElementConstructor} entityConstructor
* @param {AnyConstructor<IElement>} entityConstructor
*/
static registerElement(tagName, entityConstructor) {
ElementFactory.#elementConstructors.set(tagName, entityConstructor)

0
js/element/IDraggableControlElement.js Executable file → Normal file
View File

0
js/element/IDraggableElement.js Executable file → Normal file
View File

0
js/element/IElement.js Executable file → Normal file
View File

36
js/element/IFromToPositionedElement.js Executable file → Normal file
View File

@@ -9,19 +9,19 @@ export default class IFromToPositionedElement extends IElement {
static properties = {
...super.properties,
originX: {
fromX: {
type: Number,
attribute: false,
},
originY: {
fromY: {
type: Number,
attribute: false,
},
targetX: {
toX: {
type: Number,
attribute: false,
},
targetY: {
toY: {
type: Number,
attribute: false,
},
@@ -29,35 +29,35 @@ export default class IFromToPositionedElement extends IElement {
constructor() {
super()
this.originX = 0
this.originY = 0
this.targetX = 0
this.targetY = 0
this.fromX = 0
this.fromY = 0
this.toX = 0
this.toY = 0
}
/** @param {Coordinates} param0 */
setBothLocations([x, y]) {
this.originX = x
this.originY = y
this.targetX = x
this.targetY = y
this.fromX = x
this.fromY = y
this.toX = x
this.toY = y
}
/**
* @param {Number} x
* @param {Number} y
*/
addOriginLocation(x, y) {
this.originX += x
this.originY += y
addSourceLocation(x, y) {
this.fromX += x
this.fromY += y
}
/**
* @param {Number} x
* @param {Number} y
*/
addTargetLocation(x, y) {
this.targetX += x
this.targetY += y
addDestinationLocation(x, y) {
this.toX += x
this.toY += y
}
}

4
js/element/ISelectableDraggableElement.js Executable file → Normal file
View File

@@ -1,5 +1,5 @@
import Configuration from "../Configuration.js"
import BooleanEntity from "../entity/BooleanEntity.js"
import Utility from "../Utility.js"
import IDraggableElement from "./IDraggableElement.js"
/**
@@ -15,7 +15,7 @@ export default class ISelectableDraggableElement extends IDraggableElement {
type: Boolean,
attribute: "data-selected",
reflect: true,
converter: BooleanEntity.booleanConverter,
converter: Utility.booleanConverter,
},
}

10
js/element/InputElement.js Executable file → Normal file
View File

@@ -1,6 +1,6 @@
import BooleanEntity from "../entity/BooleanEntity.js"
import InputTemplate from "../template/pin/InputTemplate.js"
import IElement from "./IElement.js"
import InputTemplate from "../template/pin/InputTemplate.js"
import Utility from "../Utility.js"
/** @extends {IElement<Object, InputTemplate>} */
export default class InputElement extends IElement {
@@ -10,19 +10,19 @@ export default class InputElement extends IElement {
singleLine: {
type: Boolean,
attribute: "data-single-line",
converter: BooleanEntity.booleanConverter,
converter: Utility.booleanConverter,
reflect: true,
},
selectOnFocus: {
type: Boolean,
attribute: "data-select-focus",
converter: BooleanEntity.booleanConverter,
converter: Utility.booleanConverter,
reflect: true,
},
blurOnEnter: {
type: Boolean,
attribute: "data-blur-enter",
converter: BooleanEntity.booleanConverter,
converter: Utility.booleanConverter,
reflect: true,
},
}

224
js/element/LinkElement.js Executable file → Normal file
View File

@@ -1,10 +1,9 @@
import { html, nothing } from "lit"
import Configuration from "../Configuration.js"
import IFromToPositionedElement from "./IFromToPositionedElement.js"
import LinkTemplate from "../template/LinkTemplate.js"
import SVGIcon from "../SVGIcon.js"
import Utility from "../Utility.js"
import BooleanEntity from "../entity/BooleanEntity.js"
import LinkTemplate from "../template/LinkTemplate.js"
import IFromToPositionedElement from "./IFromToPositionedElement.js"
/** @extends {IFromToPositionedElement<Object, LinkTemplate>} */
export default class LinkElement extends IFromToPositionedElement {
@@ -14,34 +13,12 @@ export default class LinkElement extends IFromToPositionedElement {
dragging: {
type: Boolean,
attribute: "data-dragging",
converter: BooleanEntity.booleanConverter,
reflect: true,
},
originNode: {
type: String,
attribute: "data-origin-node",
reflect: true,
},
originPin: {
type: String,
attribute: "data-origin-pin",
reflect: true,
},
targetNode: {
type: String,
attribute: "data-target-node",
reflect: true,
},
targetPin: {
type: String,
attribute: "data-target-pin",
converter: Utility.booleanConverter,
reflect: true,
},
originatesFromInput: {
type: Boolean,
attribute: "data-from-input",
converter: BooleanEntity.booleanConverter,
reflect: true,
attribute: false,
},
svgPathD: {
type: String,
@@ -58,35 +35,30 @@ export default class LinkElement extends IFromToPositionedElement {
}
/** @type {PinElement} */
#origin
get origin() {
return this.#origin
#source
get source() {
return this.#source
}
set origin(pin) {
set source(pin) {
this.#setPin(pin, false)
}
/** @type {PinElement} */
#target
get target() {
return this.#target
#destination
get destination() {
return this.#destination
}
set target(pin) {
set destination(pin) {
this.#setPin(pin, true)
}
#nodeDeleteHandler = () => this.remove()
/** @param {UEBDragEvent} e */
#nodeDragOriginHandler = e => this.addOriginLocation(...e.detail.value)
#nodeDragSourceHandler = e => this.addSourceLocation(...e.detail.value)
/** @param {UEBDragEvent} e */
#nodeDragTargetHandler = e => this.addTargetLocation(...e.detail.value)
#nodeReflowOriginHandler = e => {
if (this.origin.isKnot()) {
this.originatesFromInput = this.origin.isInputVisually()
}
this.setOriginLocation()
}
#nodeReflowTargetHandler = e => this.setTargetLocation()
#nodeDragDestinatonHandler = e => this.addDestinationLocation(...e.detail.value)
#nodeReflowSourceHandler = e => this.setSourceLocation()
#nodeReflowDestinatonHandler = e => this.setDestinationLocation()
/** @type {TemplateResult | nothing} */
linkMessageIcon = nothing
@@ -99,10 +71,6 @@ export default class LinkElement extends IFromToPositionedElement {
constructor() {
super()
this.dragging = false
this.originNode = ""
this.originPin = ""
this.targetNode = ""
this.targetPin = ""
this.originatesFromInput = false
this.startPercentage = 0
this.svgPathD = ""
@@ -110,44 +78,44 @@ export default class LinkElement extends IFromToPositionedElement {
}
/**
* @param {PinElement} origin
* @param {PinElement?} target
* @param {PinElement} source
* @param {PinElement?} destination
*/
static newObject(origin, target) {
static newObject(source, destination) {
const result = new LinkElement()
result.initialize(origin, target)
result.initialize(source, destination)
return result
}
/**
* @param {PinElement} origin
* @param {PinElement?} target
* @param {PinElement} source
* @param {PinElement?} destination
*/
// @ts-expect-error
initialize(origin, target) {
initialize(source, destination) {
super.initialize({}, new LinkTemplate())
if (origin) {
this.origin = origin
if (!target) {
this.targetX = this.originX
this.targetY = this.originY
if (source) {
this.source = source
if (!destination) {
this.toX = this.fromX
this.toY = this.fromY
}
}
if (target) {
this.target = target
if (!origin) {
this.originX = this.targetX
this.originY = this.targetY
if (destination) {
this.destination = destination
if (!source) {
this.fromX = this.toX
this.fromY = this.toY
}
}
}
/**
* @param {PinElement} pin
* @param {Boolean} isTargetPin
* @param {Boolean} isDestinationPin
*/
#setPin(pin, isTargetPin) {
const getCurrentPin = () => isTargetPin ? this.target : this.origin
#setPin(pin, isDestinationPin) {
const getCurrentPin = () => isDestinationPin ? this.destination : this.source
if (getCurrentPin() == pin) {
return
}
@@ -156,141 +124,115 @@ export default class LinkElement extends IFromToPositionedElement {
nodeElement.removeEventListener(Configuration.removeEventName, this.#nodeDeleteHandler)
nodeElement.removeEventListener(
Configuration.nodeDragEventName,
isTargetPin ? this.#nodeDragTargetHandler : this.#nodeDragOriginHandler
isDestinationPin ? this.#nodeDragDestinatonHandler : this.#nodeDragSourceHandler
)
nodeElement.removeEventListener(
Configuration.nodeReflowEventName,
isTargetPin ? this.#nodeReflowTargetHandler : this.#nodeReflowOriginHandler
isDestinationPin ? this.#nodeReflowDestinatonHandler : this.#nodeReflowSourceHandler
)
this.#unlinkPins()
}
if (isTargetPin) {
this.#target = pin
this.targetNode = pin?.nodeElement.nodeTitle
this.targetPin = pin?.pinId.toString()
} else {
this.#origin = pin
this.originNode = pin?.nodeElement.nodeTitle
this.originPin = pin?.pinId.toString()
}
isDestinationPin
? this.#destination = pin
: this.#source = pin
if (getCurrentPin()) {
const nodeElement = getCurrentPin().getNodeElement()
nodeElement.addEventListener(Configuration.removeEventName, this.#nodeDeleteHandler)
nodeElement.addEventListener(
Configuration.nodeDragEventName,
isTargetPin ? this.#nodeDragTargetHandler : this.#nodeDragOriginHandler
isDestinationPin ? this.#nodeDragDestinatonHandler : this.#nodeDragSourceHandler
)
nodeElement.addEventListener(
Configuration.nodeReflowEventName,
isTargetPin ? this.#nodeReflowTargetHandler : this.#nodeReflowOriginHandler
isDestinationPin ? this.#nodeReflowDestinatonHandler : this.#nodeReflowSourceHandler
)
isTargetPin
? this.setTargetLocation()
: (this.setOriginLocation(), this.originatesFromInput = this.origin.isInputVisually())
isDestinationPin
? this.setDestinationLocation()
: (this.setSourceLocation(), this.originatesFromInput = this.source.isInput())
this.#linkPins()
}
}
#linkPins() {
if (this.origin && this.target) {
this.origin.linkTo(this.target)
this.target.linkTo(this.origin)
if (this.source && this.destination) {
this.source.linkTo(this.destination)
this.destination.linkTo(this.source)
}
}
#unlinkPins() {
if (this.origin && this.target) {
this.origin.unlinkFrom(this.target, false)
this.target.unlinkFrom(this.origin, false)
if (this.source && this.destination) {
this.source.unlinkFrom(this.destination, false)
this.destination.unlinkFrom(this.source, false)
}
}
cleanup() {
super.cleanup()
this.#unlinkPins()
this.origin = null
this.target = null
this.source = null
this.destination = null
}
/** @param {Coordinates} location */
setOriginLocation(location = null, canPostpone = true) {
setSourceLocation(location = null, canPostpone = true) {
if (location == null) {
const self = this
if (canPostpone && (!this.hasUpdated || !this.origin.hasUpdated)) {
Promise.all([this.updateComplete, this.origin.updateComplete])
.then(() => self.setOriginLocation(null, false))
if (canPostpone && (!this.hasUpdated || !this.source.hasUpdated)) {
Promise.all([this.updateComplete, this.source.updateComplete])
.then(() => self.setSourceLocation(null, false))
return
}
location = this.origin.template.getLinkLocation()
location = this.source.template.getLinkLocation()
}
const [x, y] = location
this.originX = x
this.originY = y
this.fromX = x
this.fromY = y
}
/** @param {Coordinates} location */
setTargetLocation(location = null, canPostpone = true) {
setDestinationLocation(location = null, canPostpone = true) {
if (location == null) {
const self = this
if (canPostpone && (!this.hasUpdated || !this.target.hasUpdated)) {
Promise.all([this.updateComplete, this.target.updateComplete])
.then(() => self.setTargetLocation(null, false))
if (canPostpone && (!this.hasUpdated || !this.destination.hasUpdated)) {
Promise.all([this.updateComplete, this.destination.updateComplete])
.then(() => self.setDestinationLocation(null, false))
return
}
location = this.target.template.getLinkLocation()
location = this.destination.template.getLinkLocation()
}
this.targetX = location[0]
this.targetY = location[1]
this.toX = location[0]
this.toY = location[1]
}
getInputPin(getSomething = false) {
if (this.origin?.isInput()) {
return this.origin
}
if (this.target?.isInput()) {
return this.target
}
if (getSomething) {
return this.origin ?? this.target
getInputPin() {
if (this.source?.isInput()) {
return this.source
}
return this.destination
}
/** @param {PinElement} pin */
setInputPin(pin) {
if (this.origin?.isInput()) {
this.origin = pin
if (this.source?.isInput()) {
this.source = pin
}
this.target = pin
this.destination = pin
}
getOutputPin(getSomething = false) {
if (this.origin?.isOutput()) {
return this.origin
}
if (this.target?.isOutput()) {
return this.target
}
if (getSomething) {
return this.origin ?? this.target
getOutputPin() {
if (this.destination?.isOutput()) {
return this.destination
}
return this.source
}
/** @param {PinElement} pin */
setOutputPin(pin) {
if (this.target?.isOutput()) {
this.target = pin
}
this.origin = pin
}
/** @param {NodeElement} node */
getOtherPin(node) {
if (this.origin?.nodeElement === node) {
return this.target
}
if (this.target?.nodeElement === node) {
return this.origin
if (this.destination?.isOutput()) {
this.destination = pin
}
this.source = pin
}
startDragging() {
@@ -308,7 +250,7 @@ export default class LinkElement extends IFromToPositionedElement {
setMessageConvertType() {
this.linkMessageIcon = SVGIcon.convert
this.linkMessageText = html`Convert ${this.origin.pinType} to ${this.target.pinType}.`
this.linkMessageText = html`Convert ${this.source.pinType} to ${this.destination.pinType}.`
}
setMessageCorrect() {

31
js/element/NodeElement.js Executable file → Normal file
View File

@@ -1,7 +1,7 @@
import Configuration from "../Configuration.js"
import Utility from "../Utility.js"
import nodeTemplateClass from "../decoding/nodeTemplate.js"
import nodeTitle from "../decoding/nodeTitle.js"
import BooleanEntity from "../entity/BooleanEntity.js"
import ObjectEntity from "../entity/ObjectEntity.js"
import PinEntity from "../entity/PinEntity.js"
import PinReferenceEntity from "../entity/PinReferenceEntity.js"
@@ -41,7 +41,7 @@ export default class NodeElement extends ISelectableDraggableElement {
},
pureFunction: {
type: Boolean,
converter: BooleanEntity.booleanConverter,
converter: Utility.booleanConverter,
attribute: "data-pure-function",
reflect: true,
},
@@ -99,15 +99,14 @@ export default class NodeElement extends ISelectableDraggableElement {
return result
}
/** @param {String} name */
#redirectLinksBeforeRename(name) {
for (let originPinElement of this.getPinElements()) {
for (let targetPinReference of originPinElement.getLinks()) {
#redirectLinksAfterRename(name) {
for (let sourcePinElement of this.getPinElements()) {
for (let targetPinReference of sourcePinElement.getLinks()) {
this.blueprint.getPin(targetPinReference).redirectLink(
originPinElement,
sourcePinElement,
new PinReferenceEntity(
new SymbolEntity(name),
originPinElement.entity.PinId,
name,
sourcePinElement.entity.PinId,
)
)
}
@@ -131,15 +130,11 @@ export default class NodeElement extends ISelectableDraggableElement {
} else {
this.updateComplete.then(() => this.computeSizes())
}
entity.listenAttribute(
"Name",
/** @param {InstanceType<typeof ObjectEntity.attributes.Name>} newName */
newName => {
this.#redirectLinksBeforeRename(newName?.toString())
this.nodeTitle = newName?.toString()
this.nodeDisplayName = nodeTitle(entity)
}
)
entity.listenAttribute("Name", name => {
this.nodeTitle = entity.Name
this.nodeDisplayName = nodeTitle(entity)
this.#redirectLinksAfterRename(name)
})
}
async getUpdateComplete() {

120
js/element/PinElement.js Executable file → Normal file
View File

@@ -1,17 +1,17 @@
import Configuration from "../Configuration.js"
import Utility from "../Utility.js"
import pinTemplate from "../decoding/pinTemplate.js"
import BooleanEntity from "../entity/BooleanEntity.js"
import ArrayEntity from "../entity/ArrayEntity.js"
import GuidEntity from "../entity/GuidEntity.js"
import LinearColorEntity from "../entity/LinearColorEntity.js"
import PinEntity from "../entity/PinEntity.js"
import PinReferenceEntity from "../entity/PinReferenceEntity.js"
import SymbolEntity from "../entity/SymbolEntity.js"
import StringEntity from "../entity/StringEntity.js"
import PinTemplate from "../template/pin/PinTemplate.js"
import ElementFactory from "./ElementFactory.js"
import IElement from "./IElement.js"
/**
* @template {IEntity} T
* @template {TerminalAttribute} T
* @extends {IElement<PinEntity<T>, PinTemplate>}
*/
export default class PinElement extends IElement {
@@ -23,7 +23,7 @@ export default class PinElement extends IElement {
fromAttribute: (value, type) => value
? GuidEntity.grammar.parse(value)
: null,
toAttribute: (value, type) => value?.toString(),
toAttribute: (value, type) => /** @type {String} */(value?.toString()),
},
attribute: "data-id",
reflect: true,
@@ -44,7 +44,7 @@ export default class PinElement extends IElement {
fromAttribute: (value, type) => value
? LinearColorEntity.getLinearColorFromAnyFormat().parse(value)
: null,
toAttribute: (value, type) => value ? LinearColorEntity.printLinearColor(value) : null,
toAttribute: (value, type) => value ? Utility.printLinearColor(value) : null,
},
attribute: "data-color",
reflect: true,
@@ -55,7 +55,7 @@ export default class PinElement extends IElement {
},
isLinked: {
type: Boolean,
converter: BooleanEntity.booleanConverter,
converter: Utility.booleanConverter,
attribute: "data-linked",
reflect: true,
},
@@ -66,7 +66,7 @@ export default class PinElement extends IElement {
},
connectable: {
type: Boolean,
converter: BooleanEntity.booleanConverter,
converter: Utility.booleanConverter,
attribute: "data-connectable",
reflect: true,
}
@@ -95,11 +95,10 @@ export default class PinElement extends IElement {
this.isLinked = false
this.connectable = !entity.bNotConnectable?.valueOf()
super.initialize(entity, template)
this.pinId = this.entity.PinId
this.pinType = this.entity.getType()
this.defaultValue = this.entity.getDefaultValue()
this.color = PinElement.properties.color.converter.fromAttribute(this.getColor().toString())
this.pinDirection = entity.isInput() ? "input" : entity.isOutput() ? "output" : "hidden"
this.updateColor()
}
setup() {
@@ -107,88 +106,42 @@ export default class PinElement extends IElement {
this.nodeElement = this.closest("ueb-node")
}
updateColor() {
this.color = PinElement.properties.color.converter.fromAttribute(this.entity.pinColor().toString())
}
createPinReference() {
return new PinReferenceEntity(new SymbolEntity(this.nodeElement.getNodeName()), this.getPinId())
return new PinReferenceEntity({
objectName: new StringEntity(this.nodeElement.getNodeName()),
pinGuid: this.getPinId(),
})
}
/** @return {GuidEntity} */
getPinId() {
return this.entity.PinId
}
/** @returns {String} */
getPinName() {
return this.entity.PinName?.toString() ?? ""
return this.entity.PinName?.valueOf() ?? ""
}
getPinDisplayName() {
return this.entity.pinTitle()
}
/** @param {PinElement} pin */
#traverseKnots(pin) {
while (pin?.isKnot()) {
const pins = pin.nodeElement.getPinElements()
pin = pin === pins[0] ? pins[1] : pins[0]
pin = pin.isLinked ? this.blueprint.getPin(pin.getLinks()[0]) : null
}
return pin?.isKnot() ? undefined : pin
/** @return {CSSResult} */
getColor() {
return this.entity.pinColor()
}
isInput(ignoreKnots = false) {
/** @type {PinElement} */
let result = this
if (ignoreKnots) {
return this.#traverseKnots(result)?.isInput()
}
return result.entity.isInput()
isInput() {
return this.entity.isInput()
}
/** @returns {boolean} True when the pin is the input part of a knot that can switch direction */
isInputLoosely() {
return this.isInput(false) && this.isInput(true) === undefined
isOutput() {
return this.entity.isOutput()
}
/** @returns {boolean} True when the pin is input and if it is a knot it appears input */
isInputVisually() {
const template = /** @type {KnotNodeTemplate} */(this.nodeElement.template)
const isKnot = this.isKnot()
return isKnot && this.isInput() != template.switchDirectionsVisually
|| !isKnot && this.isInput()
}
isOutput(ignoreKnots = false) {
/** @type {PinElement} */
let result = this
if (ignoreKnots) {
return this.#traverseKnots(result)?.isOutput()
}
return result.entity.isOutput()
}
/** @returns {boolean} True when the pin is the output part of a knot that can switch direction */
isOutputLoosely() {
return this.isOutput(false) && this.isOutput(true) === undefined
}
/** @returns {boolean} True when the pin is output and if it is a knot it appears output */
isOutputVisually() {
const template = /** @type {KnotNodeTemplate} */(this.nodeElement.template)
const isKnot = this.isKnot()
return isKnot && this.isOutput() != template.switchDirectionsVisually
|| !isKnot && this.isOutput()
}
/** @returns {value is InstanceType<PinElement<>>} */
isKnot() {
return this.nodeElement?.getType() == Configuration.paths.knot
}
getLinkLocation(oppositeDirection = false) {
return this.template.getLinkLocation(oppositeDirection)
getLinkLocation() {
return this.template.getLinkLocation()
}
getNodeElement() {
@@ -214,7 +167,7 @@ export default class PinElement extends IElement {
/** @param {IElement[]} nodesWhitelist */
sanitizeLinks(nodesWhitelist = []) {
this.entity.LinkedTo = new (PinEntity.attributes.LinkedTo)(
this.entity.LinkedTo = new ArrayEntity(
this.entity.LinkedTo?.valueOf().filter(pinReference => {
let pin = this.blueprint.getPin(pinReference)
if (pin) {
@@ -239,17 +192,14 @@ export default class PinElement extends IElement {
const pinReference = this.createPinReference()
if (
this.isLinked
&& this.entity.isExecution()
&& this.isOutput(true)
&& this.getLinks().some(ref => !pinReference.equals(ref))
) {
if (this.isKnot()) {
}
&& this.isOutput()
&& (this.pinType === "exec" || targetPinElement.pinType === "exec")
&& !this.getLinks().some(ref => pinReference.equals(ref))) {
this.unlinkFromAll()
}
if (this.entity.linkTo(targetPinElement.getNodeElement().getNodeName(), targetPinElement.entity)) {
this.isLinked = this.entity.isLinked()
this.nodeElement?.template.linksChanged()
if (this.entity.recomputesNodeTitleOnChange) {
this.nodeElement?.computeNodeDisplayName()
}
@@ -260,6 +210,7 @@ export default class PinElement extends IElement {
unlinkFrom(targetPinElement, removeLink = true) {
if (this.entity.unlinkFrom(targetPinElement.getNodeElement().getNodeName(), targetPinElement.entity)) {
this.isLinked = this.entity.isLinked()
this.nodeElement?.template.linksChanged()
if (removeLink) {
this.blueprint.getLink(this, targetPinElement)?.remove() // Might be called after the link is removed
}
@@ -270,8 +221,11 @@ export default class PinElement extends IElement {
}
unlinkFromAll() {
const isLinked = this.getLinks().length
this.getLinks().map(ref => this.blueprint.getPin(ref)).forEach(pin => this.unlinkFrom(pin))
const isLinked = false
if (isLinked) {
this.nodeElement?.template.linksChanged()
}
}
/**
@@ -281,10 +235,10 @@ export default class PinElement extends IElement {
redirectLink(originalPinElement, newReference) {
const index = this.getLinks().findIndex(pinReference =>
pinReference.objectName.toString() == originalPinElement.getNodeElement().getNodeName()
&& pinReference.pinGuid.toString() == originalPinElement.entity.PinId.toString()
&& pinReference.pinGuid.valueOf() == originalPinElement.entity.PinId.valueOf()
)
if (index >= 0) {
this.entity.LinkedTo.valueOf()[index] = newReference
this.entity.LinkedTo[index] = newReference
return true
}
return false

12
js/element/SelectorElement.js Executable file → Normal file
View File

@@ -37,16 +37,16 @@ export default class SelectorElement extends IFromToPositionedElement {
/** @param {Coordinates} finalPosition */
selectTo(finalPosition) {
this.selectionModel.selectTo(finalPosition)
this.targetX = finalPosition[0]
this.targetY = finalPosition[1]
this.toX = finalPosition[0]
this.toY = finalPosition[1]
}
endSelect() {
this.blueprint.selecting = false
this.selectionModel = null
this.originX = 0
this.originY = 0
this.targetX = 0
this.targetY = 0
this.fromX = 0
this.fromY = 0
this.toX = 0
this.toY = 0
}
}

0
js/element/WindowElement.js Executable file → Normal file
View File

0
js/element/defineElements.js Executable file → Normal file
View File

7
js/entity/AlternativesEntity.js Executable file → Normal file
View File

@@ -1,7 +1,6 @@
import P from "parsernostrum"
import IEntity from "./IEntity.js"
/** @template {(typeof IEntity)[]} T */
export default class AlternativesEntity extends IEntity {
/** @type {(typeof IEntity)[]} */
@@ -10,14 +9,14 @@ export default class AlternativesEntity extends IEntity {
static className() {
let result = super.className()
if (this.alternatives.length) {
result += ".accepting(" + this.alternatives.map(v => v.className()).join(", ") + ")"
result += " (accepting: " + this.alternatives.map(v => v.className()).join(", ") + ")"
}
return result
}
static createGrammar() {
const grammars = this.alternatives.map(entity => entity.grammar)
if (this.alternatives.length == 0 || grammars.includes(this.unknownEntityGrammar)) {
if (grammars.includes(this.unknownEntityGrammar)) {
return this.unknownEntityGrammar
}
return P.alt(...grammars)
@@ -28,7 +27,7 @@ export default class AlternativesEntity extends IEntity {
* @param {Types} types
*/
static accepting(...types) {
const result = /** @type {typeof AlternativesEntity<Types> & { alternatives: Types }} */(
const result = /** @type {typeof AlternativesEntity & { alternatives: Types }} */(
this.asUniqueClass()
)
result.alternatives = types

84
js/entity/ArrayEntity.js Executable file → Normal file
View File

@@ -2,24 +2,26 @@ import P from "parsernostrum"
import Grammar from "../serialization/Grammar.js"
import IEntity from "./IEntity.js"
/** @template {typeof IEntity} T */
/** @template {IEntity} T */
export default class ArrayEntity extends IEntity {
/** @type {typeof IEntity} */
static type
static grammar = this.createGrammar()
static grammar = /** @type {P<ArrayEntity<IEntity>>} */(
this.createGrammar()
)
get length() {
return this.values.length
}
/** @param {(ExtractType<T>)[]} values */
/** @param {T[]} values */
constructor(values = []) {
super()
this.values = values
}
/** @returns {P<ArrayEntity<typeof IEntity>>} */
/** @returns {P<ArrayEntity<IEntity>>} */
static createGrammar(elementGrammar = this.type?.grammar ?? P.lazy(() => this.unknownEntityGrammar)) {
return this.inlined
? elementGrammar
@@ -29,31 +31,18 @@ export default class ArrayEntity extends IEntity {
P.reg(/\s*(,\s*)?\)/, 1),
).map(([_0, values, trailing]) => {
values = values instanceof Array ? values : []
let Self = this
if ((trailing !== undefined) !== Self.trailing) {
Self = Self.flagTrailing(trailing !== undefined)
}
return new Self(values)
const result = new this(values)
result.trailing = trailing !== undefined
return result
}).label(`ArrayEntity of ${this.type?.className() ?? "unknown values"}`)
}
/**
* @template {typeof IEntity} T
* @this {T}
*/
static flagInlined(value = true) {
const result = this.asUniqueClass()
result.inlined = value
result.grammar = /** @type {P<ArrayEntity>} */(result.createGrammar())
return result
}
/**
* @template {typeof IEntity} T
* @param {T} type
*/
static of(type) {
const result = /** @type {{type: T, grammar: P<ArrayEntity<T>> } & typeof ArrayEntity<T>} */(
const result = /** @type {typeof ArrayEntity<ExtractType<T>> & {type: T, grammar: P<ArrayEntity<ExtractType<T>>> }} */(
this.asUniqueClass()
)
result.type = type
@@ -61,38 +50,6 @@ export default class ArrayEntity extends IEntity {
return result
}
doSerialize(
insideString = false,
indentation = "",
Self = /** @type {typeof ArrayEntity<T>} */(this.constructor),
printKey = Self.printKey,
keySeparator = Self.keySeparator,
attributeSeparator = Self.attributeSeparator,
wrap = Self.wrap,
) {
if (Self.inlined) {
return super.serialize.bind(
this.values,
insideString,
indentation,
Self,
printKey,
keySeparator,
attributeSeparator,
wrap
)()
}
let result = this.values.map(v => v?.serialize(insideString)).join(Self.attributeSeparator)
if (this.trailing) {
result += Self.attributeSeparator
}
return `(${result})`
}
valueOf() {
return this.values
}
/** @param {IEntity} other */
equals(other) {
if (!(other instanceof ArrayEntity) || this.values.length !== other.values.length) {
@@ -105,4 +62,25 @@ export default class ArrayEntity extends IEntity {
}
return true
}
valueOf() {
return this.values
}
toString(
insideString = false,
indentation = "",
Self = this.Self(),
printKey = Self.printKey,
wrap = Self.wrap,
) {
if (Self.inlined) {
return super.toString.bind(this.values, insideString, indentation, Self, printKey, wrap)()
}
let result = this.values.map(v => v?.toString(insideString)).join(Self.attributeSeparator)
if (this.trailing) {
result += Self.attributeSeparator
}
return `(${result})`
}
}

135
js/entity/BlueprintEntity.js Executable file → Normal file
View File

@@ -1,7 +1,6 @@
import Configuration from "../Configuration.js"
import Utility from "../Utility.js"
import ObjectEntity from "./ObjectEntity.js"
import ScriptVariableEntity from "./ScriptVariableEntity.js"
export default class BlueprintEntity extends ObjectEntity {
@@ -14,21 +13,9 @@ export default class BlueprintEntity extends ObjectEntity {
return this.#objectEntities
}
static attributes = {
...super.attributes,
ScriptVariables: super.attributes.ScriptVariables.asUniqueClass(true).withDefault(),
}
constructor(...args) {
super(...args)
if (!this.Name) {
this.Name = new (/** @type {typeof ObjectEntity} */(this.constructor).attributes.Name)("Blueprint")
}
}
/** @param {ObjectEntity} entity */
getHomonymObjectEntity(entity) {
const name = entity.getObjectName()
const name = entity.getObjectName(false)
return this.#objectEntities.find(entity => entity.getObjectName() == name)
}
@@ -40,16 +27,6 @@ export default class BlueprintEntity extends ObjectEntity {
return Configuration.nodeTitle(name, counter)
}
/** @param {String} name */
updateNameIndex(name) {
const match = name.match(/(.+)_(\d+)$/)
if (match) {
name = match[1]
const index = Number(match[2])
this.#objectEntitiesNameCounter.set(name, Math.max(index, this.#objectEntitiesNameCounter.get(name) ?? 0))
}
}
/** @param {ObjectEntity} entity */
addObjectEntity(entity) {
if (!this.#objectEntities.includes(entity)) {
@@ -77,121 +54,35 @@ export default class BlueprintEntity extends ObjectEntity {
return false
}
/**
* @param {ObjectReferenceEntity} variable
* @param {IEntity} entity
*/
renameScriptVariable(variable, entity) {
const name = variable.getName()
const newName = this.takeFreeName(name)
{
[true, false].forEach(v => {
/** @type {ObjectEntity} */
let object = this[Configuration.subObjectAttributeNameFromReference(variable, v)]
object.Name.value = newName
object.Name = object.Name
})
}
variable.path.replace(name, newName)
return newName
}
/**
* @param {ScriptVariableEntity} scriptVariableEntity
* @returns {String}
*/
variableName(scriptVariableEntity) {
return this[Configuration.subObjectAttributeNameFromReference(scriptVariableEntity.ScriptVariable, true)]
?.["Variable"]
?.["Name"]
?.toString()
}
/** @param {String} variableName */
variableIndex(variableName) {
let i = 0
for (const v of this.ScriptVariables?.valueOf()) {
if (variableName == this.variableName(v)) {
return i
}
++i
}
}
/** @param {ObjectEntity} entity */
mergeWith(entity) {
if ((entity.ScriptVariables?.length ?? 0) === 0) {
// The entity does not add new variables
if (!entity.ScriptVariables || entity.ScriptVariables.length === 0) {
return this
}
const variableObjectNames = this.ScriptVariables.valueOf().map(v => v.ScriptVariable.getName())
if (!this.ScriptVariables || this.ScriptVariables.length === 0) {
this.ScriptVariables = entity.ScriptVariables
}
let scriptVariables = Utility.mergeArrays(
this.ScriptVariables.valueOf(),
entity.ScriptVariables.valueOf(),
(l, r) => this.variableName(l) == this.variableName(r),
added => {
let name = added.ScriptVariable.getName()
if (variableObjectNames.includes(name)) {
name = this.renameScriptVariable(added.ScriptVariable, entity)
}
this.updateNameIndex(name)
}
(l, r) => l.OriginalChangeId.value == r.OriginalChangeId.value
)
if (scriptVariables.length === this.ScriptVariables.length) {
// The entity does not add new variables
return this
}
scriptVariables.reverse()
const blueprintEntity = /** @type {typeof BlueprintEntity} */(this.constructor)
const entries = scriptVariables.concat(scriptVariables).map((v, i) => {
const name = Configuration.subObjectAttributeNameFromReference(
v.ScriptVariable,
i >= scriptVariables.length // First take all the small objects then all name only
)
const object = this[name] ?? entity[name]
return object ? [name, object] : null
const name = Configuration.subObjectAttributeNameFromReference(v.ScriptVariable, i >= scriptVariables.length)
return [
name,
this[name] ?? entity[name]
]
})
.filter(v => v)
entries.push(
...Object.entries(this).filter(([k, v]) =>
!k.startsWith(Configuration.subObjectAttributeNamePrefix)
&& k !== "ExportedNodes"
),
["ScriptVariables", new (blueprintEntity.attributes.ScriptVariables)(scriptVariables.reverse())]
)
const result = new BlueprintEntity(Object.fromEntries(entries))
result.mirrorNameInExportPaths(entity.Name?.toString())
result.#objectEntitiesNameCounter = this.#objectEntitiesNameCounter
result.#objectEntities = this.#objectEntities
return result
}
/** @param {ObjectEntity[]} entities */
getVariablesAttributesReferringTo(...entities) {
let pins = new Set(...entities.flatMap(entity => entity.getPinEntities()).map(pin => pin.PinName.toString()))
let attributes = this.ScriptVariables
.valueOf()
.map(v => {
const keySimple = Configuration.subObjectAttributeNameFromReference(v.ScriptVariable, false)
const keyFull = Configuration.subObjectAttributeNameFromReference(v.ScriptVariable, true)
return {
simple: [keySimple, this[keySimple]],
full: [keyFull, this[keyFull]],
variable: v,
}
})
.filter(v => pins.has(v.full?.["Variable"]?.["Name"]))
.reduce(
(acc, cur) => {
acc.simple.push([cur.simple[0], cur.simple[1]])
acc.full.push([cur.full[0], cur.full[1]])
acc.ScriptVariables.push(cur.variable)
return acc
},
({ simple: [], full: [], ScriptVariables: [] })
)
return {
}
)
return new BlueprintEntity(Object.fromEntries(entries))
}
}

View File

@@ -3,21 +3,15 @@ import IEntity from "./IEntity.js"
export default class BooleanEntity extends IEntity {
static grammar = this.createGrammar()
static booleanConverter = {
fromAttribute: (value, type) => {
value ? "true" : "false"
},
toAttribute: (value, type) => {
if (value === true) {
return "true"
}
if (value === false) {
return "false"
}
return ""
}
}
static grammar = /** @type {P<BooleanEntity>} */(
P.regArray(/(true)|(True)|(false)|(False)/)
.map(v => {
const result = (v[1] ?? v[2]) ? new this(true) : new this(false)
result.uppercase = (v[2] ?? v[4]) !== undefined
return result
})
.label("BooleanEntity")
)
#uppercase = true
get uppercase() {
@@ -27,37 +21,22 @@ export default class BooleanEntity extends IEntity {
this.#uppercase = value
}
/** @returns {P<BooleanEntity>} */
static createGrammar() {
return P.regArray(/(true)|(True)|(false)|(False)/)
.map(v => {
const result = (v[1] ?? v[2]) ? new this(true) : new this(false)
result.uppercase = (v[2] ?? v[4]) !== undefined
return result
})
.label("BooleanEntity")
}
constructor(value = false) {
super()
this.value = value
}
serialize(
insideString = false,
indentation = "",
Self = /** @type {typeof IEntity} */(this.constructor),
) {
let result = this.value
? this.#uppercase ? "True" : "true"
: this.#uppercase ? "False" : "false"
if (Self.serialized) {
result = `"${result}"`
}
return result
}
valueOf() {
return this.value
}
toString() {
return this.value
? this.#uppercase
? "True"
: "true"
: this.#uppercase
? "False"
: "false"
}
}

View File

@@ -3,7 +3,9 @@ import IntegerEntity from "./IntegerEntity.js"
export default class ByteEntity extends IntegerEntity {
static grammar = this.createGrammar()
static grammar = /** @type {P<ByteEntity>} */(
P.numberByte.map(v => new this(v))
)
get value() {
return super.value
@@ -14,10 +16,4 @@ export default class ByteEntity extends IntegerEntity {
super.value = value
}
}
/** @returns {P<ByteEntity>} */
createGrammar() {
// @ts-expect-error
return P.numberByte.map(v => new this(v))
}
}

23
js/entity/ColorChannelEntity.js Executable file → Normal file
View File

@@ -3,35 +3,20 @@ import IEntity from "./IEntity.js"
export default class ColorChannelEntity extends IEntity {
static grammar = this.createGrammar()
static grammar = /** @type {P<ColorChannelEntity>} */(
P.number.map(v => new this(v))
)
constructor(value = 0) {
super()
this.value = value
}
/** @returns {P<ColorChannelEntity>} */
static createGrammar() {
return P.number.map(v => new this(v))
}
serialize(
insideString = false,
indentation = "",
Self = /** @type {typeof IEntity} */(this.constructor),
) {
let result = this.value.toFixed(6)
if (Self.serialized) {
result = `"${result}"`
}
return result
}
valueOf() {
return this.value
}
toString() {
return this.value.toString()
return this.value.toFixed(6)
}
}

11
js/entity/ComputedTypeEntity.js Executable file → Normal file
View File

@@ -1,16 +1,11 @@
import IEntity from "./IEntity.js"
import StringEntity from "./StringEntity.js"
export default class ComputedTypeEntity extends IEntity {
static grammar = this.createGrammar()
static grammar = undefined
/** @type {(entity: IEntity) => typeof IEntity} */
static f
static createGrammar() {
return StringEntity.grammar
}
/**
* @template {typeof ComputedTypeEntity.f} T
* @param {T} producer
@@ -22,7 +17,7 @@ export default class ComputedTypeEntity extends IEntity {
}
/** @param {IEntity} entity */
static compute(entity) {
return this.f(entity)
compute(entity) {
return /** @type {typeof ComputedTypeEntity} */(this.Self()).f(entity)
}
}

View File

@@ -4,10 +4,7 @@ import EnumEntity from "./EnumEntity.js"
export default class EnumDisplayValueEntity extends EnumEntity {
static grammar = this.createGrammar()
/** @returns {P<EnumDisplayValueEntity>} */
static createGrammar() {
return P.reg(Grammar.Regex.InsideString).map(v => new this(v))
}
static grammar = /** @type {P<EnumDisplayValueEntity>} */(
P.reg(Grammar.Regex.InsideString).map(v => new this(v))
)
}

View File

@@ -4,10 +4,7 @@ import SymbolEntity from "./SymbolEntity.js"
export default class EnumEntity extends SymbolEntity {
static grammar = this.createGrammar()
/** @returns {P<EnumEntity>} */
static createGrammar() {
return Grammar.symbol.map(v => new this(v))
}
static grammar = /** @type {P<EnumEntity>} */(
Grammar.symbol.map(v => new this(v))
)
}

53
js/entity/FormatTextEntity.js Executable file → Normal file
View File

@@ -8,17 +8,8 @@ export default class FormatTextEntity extends IEntity {
static attributeSeparator = ", "
static lookbehind = ["LOCGEN_FORMAT_NAMED", "LOCGEN_FORMAT_ORDERED"]
static grammar = this.createGrammar()
/** @param {(StringEntity | LocalizedTextEntity | InvariantTextEntity | FormatTextEntity)[]} values */
constructor(values) {
super()
this.values = values
}
/** @returns {P<FormatTextEntity>} */
static createGrammar() {
return P.lazy(() => P.seq(
static grammar = /** @type {P<FormatTextEntity>} */(
P.lazy(() => P.seq(
// Resulting regex: /(LOCGEN_FORMAT_NAMED|LOCGEN_FORMAT_ORDERED)\s*/
P.reg(new RegExp(String.raw`(${this.lookbehind.join("|")})\s*\(\s*`), 1),
P.alt(
@@ -32,26 +23,16 @@ export default class FormatTextEntity extends IEntity {
return result
}))
.label("FormatTextEntity")
)
/** @param {(StringEntity | LocalizedTextEntity | InvariantTextEntity | FormatTextEntity)[]} values */
constructor(values) {
super()
this.values = values
}
doSerialize(
insideString = false,
indentation = "",
Self = /** @type {typeof FormatTextEntity} */(this.constructor),
printKey = Self.printKey,
keySeparator = Self.keySeparator,
attributeSeparator = Self.attributeSeparator,
wrap = Self.wrap,
) {
const separator = Self.attributeSeparator
return this.lookbehind + "("
+ this.values.map(v => v.serialize(insideString)).join(separator)
+ (Self.trailing ? separator : "")
+ ")"
}
toString() {
const pattern = this.values?.[0]?.toString() // The pattern is always the first element of the array
valueOf() {
const pattern = this.values?.[0]?.valueOf() // The pattern is always the first element of the array
if (!pattern) {
return ""
}
@@ -73,4 +54,18 @@ export default class FormatTextEntity extends IEntity {
: ""
return result
}
toString(
insideString = false,
indentation = "",
Self = this.Self(),
printKey = Self.printKey,
wrap = Self.wrap,
) {
const separator = Self.attributeSeparator
return this.lookbehind + "("
+ this.values.map(v => v.toString(insideString)).join(separator)
+ (Self.trailing ? separator : "")
+ ")"
}
}

View File

@@ -13,7 +13,9 @@ export default class FunctionReferenceEntity extends IEntity {
MemberName: StringEntity,
MemberGuid: GuidEntity,
}
static grammar = this.createGrammar()
static grammar = /** @type {P<FunctionReferenceEntity>} */(
Grammar.createEntityGrammar(this, Grammar.commaSeparation, false, 0)
)
constructor(values) {
super(values)
@@ -21,9 +23,4 @@ export default class FunctionReferenceEntity extends IEntity {
/** @type {InstanceType<typeof FunctionReferenceEntity.attributes.MemberName>} */ this.MemberName
/** @type {InstanceType<typeof FunctionReferenceEntity.attributes.MemberGuid>} */ this.MemberGuid
}
/** @returns {P<FunctionReferenceEntity>} */
static createGrammar() {
return Grammar.createEntityGrammar(this, Grammar.commaSeparation, 0, 0)
}
}

View File

@@ -3,7 +3,6 @@ import P from "parsernostrum"
var crypto
if (typeof window === "undefined") {
// When used in nodejs, mainly for test purpose
import("crypto").then(mod => crypto = mod.default).catch()
} else {
crypto = window.crypto
@@ -11,7 +10,9 @@ if (typeof window === "undefined") {
export default class GuidEntity extends IEntity {
static grammar = this.createGrammar()
static grammar = /** @type {P<GuidEntity>} */(
P.reg(/[0-9A-F]{32}/i).map(v => new this(v)).label("GuidEntity")
)
static generateGuid() {
let values = new Uint32Array(4)
@@ -28,21 +29,8 @@ export default class GuidEntity extends IEntity {
this.value = value
}
/** @returns {P<GuidEntity>} */
static createGrammar() {
return P.reg(/[0-9A-F]{32}/i).map(v => new this(v)).label("GuidEntity")
}
serialize(
insideString = false,
indentation = "",
Self = /** @type {typeof IEntity} */(this.constructor),
) {
let result = this.value
if (Self.serialized) {
result = `"${result}"`
}
return result
valueOf() {
return this.value
}
toString() {

305
js/entity/IEntity.js Executable file → Normal file
View File

@@ -15,7 +15,8 @@ export default class IEntity {
static keySeparator = "="
/** @type {(k: String) => String} */
static printKey = k => k
static grammar = P.lazy(() => this.createGrammar())
/** @type {P<IEntity>} */
static grammar = P.lazy(() => this.unknownEntity)
/** @type {P<IEntity>} */
static unknownEntityGrammar
static unknownEntity
@@ -30,11 +31,26 @@ export default class IEntity {
static serialized = false // Value is written and read as string
static expected = false // Must be there
static inlined = false // The key is a subobject or array and printed as inlined (A.B=123, A(0)=123)
/** @type {Boolean} */
static quoted // Key is serialized with quotes
static quoted = false // Key is serialized with quotes
static silent = false // Do not serialize if default
static trailing = false // Add attribute separator after the last attribute when serializing
#trailing = this.Self().trailing
get trailing() {
return this.#trailing
}
set trailing(value) {
this.#trailing = value
}
#lookbehind = /** @type {String} */(this.Self().lookbehind)
get lookbehind() {
return this.#lookbehind.trim()
}
set lookbehind(value) {
this.#lookbehind = value
}
/** @type {String[]} */
#keys
get keys() {
@@ -44,50 +60,8 @@ export default class IEntity {
this.#keys = [... new Set(value)]
}
// @ts-expect-error
#lookbehind = /** @type {String} */(this.constructor.lookbehind)
get lookbehind() {
return this.#lookbehind.trim()
}
set lookbehind(value) {
this.#lookbehind = value
}
#ignored = /** @type {typeof IEntity} */(this.constructor).ignored
get ignored() {
return this.#ignored
}
set ignored(value) {
this.#ignored = value
}
#inlined = /** @type {typeof IEntity} */(this.constructor).inlined
get inlined() {
return this.#inlined
}
set inlined(value) {
this.#inlined = value
}
#quoted
get quoted() {
return this.#quoted ?? /** @type {typeof IEntity} */(this.constructor).quoted ?? false
}
set quoted(value) {
this.#quoted = value
}
/** @type {Boolean} */
#trailing
get trailing() {
return this.#trailing ?? /** @type {typeof IEntity} */(this.constructor).trailing ?? false
}
set trailing(value) {
this.#trailing = value
}
constructor(values = {}) {
const attributes = /** @type {typeof IEntity} */(this.constructor).attributes
const attributes = this.Self().attributes
const keys = Utility.mergeArrays(
Object.keys(values),
Object.entries(attributes).filter(([k, v]) => v.default !== undefined).map(([k, v]) => k)
@@ -100,16 +74,7 @@ export default class IEntity {
attributes[key] !== undefined ? attributes[key] : IEntity.unknownEntity
)(values[key])
}
const computedEntity = /** @type {ComputedTypeEntityConstructor} */(attributes[key])
this[key] = values[key]
if (computedEntity?.compute) {
/** @type {typeof IEntity} */
const actualEntity = computedEntity.compute(this)
const parsed = actualEntity.grammar.run(values[key].toString())
if (parsed.status) {
this[key] = parsed.value
}
}
continue
}
const attribute = attributes[key]
@@ -120,24 +85,12 @@ export default class IEntity {
}
}
/**
* @protected
* @returns {P<IEntity>}
*/
static createGrammar() {
return this.unknownEntityGrammar
}
static actualClass() {
static className() {
let self = this
while (!self.name) {
self = Object.getPrototypeOf(self)
}
return self
}
static className() {
return this.actualClass().name
return self.name
}
/**
@@ -146,15 +99,12 @@ export default class IEntity {
* @this {T}
* @returns {T}
*/
static asUniqueClass(alwaysCreate = false) {
let result = this
if (this.name.length || alwaysCreate) {
static asUniqueClass() {
if (this.name.length) {
// @ts-expect-error
result = (() => class extends this { })() // Comes from a lambda otherwise the class will have name "result"
result.grammar = result.createGrammar() // Reassign grammar to capture the correct this from subclass
return class extends this { }
}
return result
return this
}
/**
@@ -171,10 +121,8 @@ export default class IEntity {
/**
* @template {typeof IEntity} T
* @this {T}
* @param {(type: T) => (InstanceType<T> | NullEntity)} value
* @returns {T}
*/
static withDefault(value = type => new type()) {
static withDefault(value = /** @type {(type: T) => (InstanceType<T> | NullEntity)} */(type => new type())) {
const result = this.asUniqueClass()
result.default = value
return result
@@ -190,16 +138,6 @@ export default class IEntity {
return result
}
/**
* @template {typeof IEntity} T
* @this {T}
*/
static flagIgnored(value = true) {
const result = this.asUniqueClass()
result.ignored = value
return result
}
/**
* @template {typeof IEntity} T
* @this {T}
@@ -242,32 +180,22 @@ export default class IEntity {
/**
* @template {typeof IEntity} T
* @this {T}
* @this {InstanceType<T>}
*/
static flagTrailing(value = true) {
const result = this.asUniqueClass()
result.trailing = value
return result
}
/**
* @protected
* @param {String} string
*/
static asSerializedString(string) {
return `"${string.replaceAll(/(?<=(?:[^\\]|^)(?:\\\\)*?)"/g, '\\"')}"`
Self() {
return /** @type {T} */(this.constructor)
}
/** @param {String} key */
showProperty(key) {
/** @type {IEntity} */
let value = this[key]
const valueType = /** @type {typeof IEntity} */(value.constructor)
if (valueType.silent && valueType.default !== undefined) {
if (valueType["#default"] === undefined) {
valueType["#default"] = valueType.default(valueType)
const Self = this.Self()
if (Self.silent && Self.default !== undefined) {
if (Self["#default"] === undefined) {
Self["#default"] = Self.default(Self)
}
const defaultValue = valueType["#default"]
const defaultValue = Self["#default"]
return !value.equals(defaultValue)
}
return true
@@ -275,130 +203,52 @@ export default class IEntity {
/**
*
* @param {String} attributeName
* @param {String} attribute
* @param {(v: any) => void} callback
*/
listenAttribute(attributeName, callback) {
const descriptor = Object.getOwnPropertyDescriptor(this, attributeName)
listenAttribute(attribute, callback) {
const descriptor = Object.getOwnPropertyDescriptor(this, attribute)
const setter = descriptor.set
if (setter) {
descriptor.set = v => {
setter(v)
callback(v)
}
Object.defineProperties(this, { [attributeName]: descriptor })
Object.defineProperties(this, { [attribute]: descriptor })
} else if (descriptor.value) {
Object.defineProperties(this, {
["#" + attributeName]: {
["#" + attribute]: {
value: descriptor.value,
writable: true,
enumerable: false,
},
[attributeName]: {
[attribute]: {
enumerable: true,
get() {
return this["#" + attributeName]
return this["#" + attribute]
},
set(v) {
callback(v)
this["#" + attributeName] = v
if (v != this["#" + attribute]) {
callback(v)
this["#" + attribute] = v
}
},
},
})
}
}
/** @this {IEntity | Array} */
doSerialize(
insideString = false,
indentation = "",
Self = /** @type {typeof IEntity} */(this.constructor),
printKey = Self.printKey,
keySeparator = Self.keySeparator,
attributeSeparator = Self.attributeSeparator,
wrap = Self.wrap,
) {
const isSelfOverriden = Self !== this.constructor
let result = ""
let first = true
const keys = this instanceof IEntity ? this.keys : Object.keys(this)
for (const key of keys) {
/** @type {IEntity} */
const value = this[key]
const valueType = /** @type {typeof IEntity} */(value?.constructor)
if (value === undefined || this instanceof IEntity && !this.showProperty(key)) {
continue
}
if (first) {
first = false
} else {
result += attributeSeparator
}
let keyValue = this instanceof Array ? `(${key})` : key
if (keyValue.length && (Self.attributes[key]?.quoted || value.quoted)) {
keyValue = `"${keyValue}"`
}
if (value.inlined) {
const inlinedPrintKey = valueType.className() === "ArrayEntity"
? k => printKey(`${keyValue}${k}`)
: k => printKey(`${keyValue}.${k}`)
result += value.serialize(
insideString,
indentation,
undefined,
inlinedPrintKey,
keySeparator,
attributeSeparator,
Self.notWrapped
)
continue
}
keyValue = printKey(keyValue)
if (keyValue.length) {
result += (attributeSeparator.includes("\n") ? indentation : "") + keyValue + keySeparator
}
let serialization = value?.serialize(insideString, indentation)
result += serialization
}
if (this instanceof IEntity && (isSelfOverriden && Self.trailing || this.trailing) && result.length) {
result += attributeSeparator
}
return wrap(/** @type {IEntity} */(this), result)
}
/** @this {IEntity | Array} */
serialize(
insideString = false,
indentation = "",
Self = /** @type {typeof IEntity} */(this.constructor),
printKey = Self.printKey,
keySeparator = Self.keySeparator,
attributeSeparator = Self.attributeSeparator,
wrap = Self.wrap,
) {
const isSelfOverriden = Self !== this.constructor
let result = this instanceof Array
? IEntity.prototype.doSerialize.bind(this)(insideString, indentation, Self, printKey, keySeparator, attributeSeparator, wrap)
: this.doSerialize(insideString, indentation, Self, printKey, keySeparator, attributeSeparator, wrap)
if (Self.serialized) {
result = IEntity.asSerializedString(result)
}
return result
}
/** @param {IEntity} other */
equals(other) {
if (!(other instanceof IEntity)) {
return false
}
const thisKeys = Object.keys(this)
const otherKeys = Object.keys(other)
const thisType = /** @type {typeof IEntity} */(this.constructor).actualClass()
const otherType = /** @type {typeof IEntity} */(other.constructor).actualClass()
if (
thisKeys.length !== otherKeys.length
|| this.lookbehind != other.lookbehind
|| !(other instanceof thisType) && !(this instanceof otherType)
|| !(this instanceof other.constructor) && !(other instanceof this.constructor)
) {
return false
}
@@ -413,15 +263,6 @@ export default class IEntity {
if (!a.equals(b)) {
return false
}
} else if (a instanceof Array && b instanceof Array) {
if (a.length !== b.length) {
return false
}
for (let j = 0; j < a.length; ++j) {
if (!(a[j] instanceof IEntity && a[j].equals(b[j])) && a[j] !== b[j]) {
return false
}
}
} else {
if (a !== b) {
return false
@@ -431,8 +272,52 @@ export default class IEntity {
return true
}
/** @returns {IEntity | Boolean | Number | String | BigInt | (IEntity | Boolean | Number | String | BigInt)[]} */
valueOf() {
return this
/** @this {IEntity | Array} */
toString(
insideString = false,
indentation = "",
Self = this.Self(),
printKey = Self.printKey,
wrap = Self.wrap,
) {
let result = ""
let first = true
const keys = this instanceof IEntity ? this.keys : Object.keys(this)
for (const key of keys) {
/** @type {IEntity} */
const value = this[key]
let keyValue = this instanceof Array ? `(${key})` : key
if (value === undefined || this instanceof IEntity && !this.showProperty(key)) {
continue
}
if (first) {
first = false
} else {
result += Self.attributeSeparator
}
if (value.Self?.().inlined) {
const inlinedPrintKey = value.Self().className() === "ArrayEntity"
? k => printKey(`${keyValue}${k}`)
: k => printKey(`${keyValue}.${k}`)
result += value.toString(insideString, indentation, Self, inlinedPrintKey, Self.notWrapped)
continue
}
keyValue = printKey(keyValue)
if (keyValue.length) {
if (Self.quoted) {
keyValue = `"${keyValue}"`
}
result += (Self.attributeSeparator.includes("\n") ? indentation : "") + keyValue + Self.keySeparator
}
let serialization = value?.toString(insideString, indentation)
if (Self.serialized) {
serialization = `"${serialization.replaceAll(/(?<=(?:[^\\]|^)(?:\\\\)*?)"/, '\\"')}"`
}
result += serialization
}
if (this instanceof IEntity && this.trailing && result.length) {
result += Self.attributeSeparator
}
return wrap(/** @type {IEntity} */(this), result)
}
}

View File

@@ -3,19 +3,18 @@ import IEntity from "./IEntity.js"
export default class Integer64Entity extends IEntity {
static grammar = this.createGrammar()
static grammar = /** @type {P<Integer64Entity>} */(
P.numberBigInteger.map(v => new this(v))
)
/**
* @protected
* @type {bigint}
*/
_value
/** @type {bigint} */
#value
get value() {
return this._value
return this.#value
}
set value(value) {
if (value >= -(1n << 63n) && value < 1n << 63n) {
this._value = value
this.#value = value
}
}
@@ -25,28 +24,17 @@ export default class Integer64Entity extends IEntity {
this.value = BigInt(value)
}
/** @returns {P<Integer64Entity>} */
static createGrammar() {
return P.numberBigInteger.map(v => new this(v))
}
serialize(
insideString = false,
indentation = "",
Self = /** @type {typeof IEntity} */(this.constructor),
) {
let result = this.value.toString()
if (Self.serialized) {
result = `"${result}"`
}
return result
}
valueOf() {
return this.value
}
toString() {
toString(
insideString = false,
indentation = "",
Self = this.Self(),
printKey = Self.printKey,
wrap = Self.wrap,
) {
return this.value.toString()
}
}

View File

@@ -3,7 +3,9 @@ import NumberEntity from "./NumberEntity.js"
export default class IntegerEntity extends NumberEntity {
static grammar = this.createGrammar()
static grammar = /** @type {P<IntegerEntity>} */(
P.numberInteger.map(v => new this(v))
)
get value() {
return super.value
@@ -15,9 +17,4 @@ export default class IntegerEntity extends NumberEntity {
super.value = value
}
}
/** @returns {P<IntegerEntity>} */
static createGrammar() {
return P.numberInteger.map(v => new this(v))
}
}

25
js/entity/InvariantTextEntity.js Executable file → Normal file
View File

@@ -5,29 +5,22 @@ export default class InvariantTextEntity extends IEntity {
static lookbehind = "INVTEXT"
static grammar = this.createGrammar()
constructor(value = "") {
super()
this.value = value
}
/** @returns {P<InvariantTextEntity>} */
static createGrammar() {
return P.alt(
static grammar = /** @type {P<InvariantTextEntity>} */(
P.alt(
P.seq(
P.reg(new RegExp(`${this.lookbehind}\\s*\\(`)),
P.doubleQuotedString,
P.reg(/\s*\)/)
).map(([_0, value, _2]) => value),
P.reg(new RegExp(this.lookbehind)).map(() => "") // InvariantTextEntity can have no arguments
).map(([_0, value, _2]) => Number(value)),
P.reg(new RegExp(this.lookbehind)).map(() => 0) // InvariantTextEntity can not have arguments
)
.map(value => new this(value))
.label("InvariantTextEntity")
}
)
doSerialize() {
return this.lookbehind + '("' + this.value + '")'
constructor(value = "") {
super()
this.value = value
}
valueOf() {
@@ -35,6 +28,6 @@ export default class InvariantTextEntity extends IEntity {
}
toString() {
return this.value
return this.lookbehind + "(" + this.value + ")"
}
}

15
js/entity/KeyBindingEntity.js Executable file → Normal file
View File

@@ -16,7 +16,12 @@ export default class KeyBindingEntity extends IEntity {
bCmd: BooleanEntity,
Key: SymbolEntity,
}
static grammar = this.createGrammar()
static grammar = /** @type {P<KeyBindingEntity>} */(
P.alt(
SymbolEntity.grammar.map(identifier => new this({ Key: identifier })),
Grammar.createEntityGrammar(this)
)
)
constructor(values) {
super(values)
@@ -27,12 +32,4 @@ export default class KeyBindingEntity extends IEntity {
/** @type {InstanceType<typeof KeyBindingEntity.attributes.bCmd>} */ this.bCmd
/** @type {InstanceType<typeof KeyBindingEntity.attributes.Key>} */ this.Key
}
/** @returs {P<KeyBindingEntity>} */
static createGrammar() {
return P.alt(
SymbolEntity.grammar.map(identifier => new this({ Key: identifier })),
Grammar.createEntityGrammar(this),
)
}
}

24
js/entity/LinearColorEntity.js Executable file → Normal file
View File

@@ -14,7 +14,9 @@ export default class LinearColorEntity extends IEntity {
B: ColorChannelEntity.withDefault(),
A: ColorChannelEntity.withDefault(type => new type(1)),
}
static grammar = this.createGrammar()
static grammar = /** @type {P<LinearColorEntity>} */(
Grammar.createEntityGrammar(this).label("LinearColorEntity")
)
#H = new ColorChannelEntity()
get H() {
@@ -57,16 +59,6 @@ export default class LinearColorEntity extends IEntity {
this.#updateHSV()
}
/** @returns {P<LinearColorEntity>} */
static createGrammar() {
return Grammar.createEntityGrammar(this, Grammar.commaSeparation, 0.5).label("LinearColorEntity")
}
/** @param {LinearColorEntity} value */
static printLinearColor(value) {
return `${Math.round(value.R.valueOf() * 255)}, ${Math.round(value.G.valueOf() * 255)}, ${Math.round(value.B.valueOf() * 255)}`
}
/** @param {Number} x */
static linearToSRGB(x) {
if (x <= 0) {
@@ -299,11 +291,6 @@ export default class LinearColorEntity extends IEntity {
+ Math.round(this.A.value * 0xff)
}
/** @returns {[Number, Number, Number, Number]} */
toArray() {
return [this.R.value, this.G.value, this.B.value, this.A.value]
}
/** @param {Number} number */
setFromRGBANumber(number) {
this.A.value = (number & 0xff) / 0xff
@@ -322,7 +309,8 @@ export default class LinearColorEntity extends IEntity {
this.#updateHSV()
}
toString() {
return LinearColorEntity.printLinearColor(this)
/** @returns {[Number, Number, Number, Number]} */
toArray() {
return [this.R.value, this.G.value, this.B.value, this.A.value]
}
}

View File

@@ -1,8 +1,8 @@
import P from "parsernostrum"
import Utility from "../Utility.js"
import Grammar from "../serialization/Grammar.js"
import IEntity from "./IEntity.js"
import StringEntity from "./StringEntity.js"
import IEntity from "./IEntity.js"
export default class LocalizedTextEntity extends IEntity {
@@ -15,18 +15,8 @@ export default class LocalizedTextEntity extends IEntity {
key: StringEntity.withDefault(),
value: StringEntity.withDefault(),
}
static grammar = this.createGrammar()
constructor(values = {}) {
super(values)
/** @type {InstanceType<typeof LocalizedTextEntity.attributes.namespace>} */ this.namespace
/** @type {InstanceType<typeof LocalizedTextEntity.attributes.key>} */ this.key
/** @type {InstanceType<typeof LocalizedTextEntity.attributes.value>} */ this.value
}
/** @returns {P<LocalizedTextEntity>} */
static createGrammar() {
return P.regArray(new RegExp(
static grammar = /** @type {P<LocalizedTextEntity>} */(
P.regArray(new RegExp(
String.raw`${LocalizedTextEntity.lookbehind}\s*\(`
+ String.raw`\s*"(?<namespace>${Grammar.Regex.InsideString.source})"\s*,`
+ String.raw`\s*"(?<key>${Grammar.Regex.InsideString.source})"\s*,`
@@ -42,9 +32,16 @@ export default class LocalizedTextEntity extends IEntity {
trailing: trailing !== undefined,
})
}).label("LocalizedTextEntity")
)
constructor(values = {}) {
super(values)
/** @type {InstanceType<typeof LocalizedTextEntity.attributes.namespace>} */ this.namespace
/** @type {InstanceType<typeof LocalizedTextEntity.attributes.key>} */ this.key
/** @type {InstanceType<typeof LocalizedTextEntity.attributes.value>} */ this.value
}
toString() {
valueOf() {
return Utility.capitalFirstLetter(this.value.valueOf())
}
}

View File

@@ -12,7 +12,9 @@ export default class MacroGraphReferenceEntity extends IEntity {
GraphBlueprint: ObjectReferenceEntity,
GraphGuid: GuidEntity,
}
static grammar = this.createGrammar()
static grammar = /** @type {P<MacroGraphReferenceEntity>} */(
Grammar.createEntityGrammar(this)
)
constructor(values) {
super(values)
@@ -21,11 +23,6 @@ export default class MacroGraphReferenceEntity extends IEntity {
/** @type {InstanceType<typeof MacroGraphReferenceEntity.attributes.GraphGuid>} */ this.GraphGuid
}
/** @returns {P<MacroGraphReferenceEntity>} */
static createGrammar() {
return Grammar.createEntityGrammar(this)
}
getMacroName() {
const colonIndex = this.MacroGraph.path.search(":")
return this.MacroGraph.path.substring(colonIndex + 1)

64
js/entity/MirroredEntity.js Executable file → Normal file
View File

@@ -1,4 +1,3 @@
import P from "parsernostrum"
import IEntity from "./IEntity.js"
/** @template {typeof IEntity} T */
@@ -10,69 +9,38 @@ export default class MirroredEntity extends IEntity {
/** @param {() => InstanceType<T>} getter */
constructor(getter = null) {
super()
const self = /** @type {typeof MirroredEntity<T>} */(this.constructor)
getter ??= self.default !== undefined ? /** @type {MirroredEntity} */(self.default(self)).getter : getter
this.getter = getter
}
static createGrammar(elementGrammar = this.type?.grammar ?? P.lazy(() => this.unknownEntityGrammar)) {
return this.type?.grammar.map(v => new this(() => v))
}
/**
* @template {typeof IEntity} T
* @this {T}
* @param {(type: T) => (InstanceType<T> | NullEntity)} value
* @returns {T}
*/
// @ts-expect-error
static withDefault(value = type => new type(() => new (type.type)())) {
// @ts-expect-error
return super.withDefault(value)
}
/**
* @template {typeof IEntity} T
* @param {T} type
* @returns {typeof MirroredEntity<T>}
*/
static of(type) {
const result = /** @type {{type: T, grammar: P<MirroredEntity<T>> } & typeof MirroredEntity<T>} */(
this.asUniqueClass()
)
const result = this.asUniqueClass()
result.type = type
result.grammar = result.createGrammar()
result.grammar = result.getTargetType().grammar.map(v => new this())
return result
}
doSerialize(
/** @returns {typeof IEntity} */
static getTargetType() {
const result = this.type
if (result.prototype instanceof MirroredEntity) {
return /** @type {typeof MirroredEntity} */(result).getTargetType()
}
return result
}
toString(
insideString = false,
indentation = "",
Self = /** @type {typeof MirroredEntity<T>} */(this.constructor),
Self = this.Self(),
printKey = Self.printKey,
keySeparator = Self.keySeparator,
attributeSeparator = Self.attributeSeparator,
wrap = Self.wrap,
) {
const value = this.getter()
return value.serialize(insideString, indentation, Self.type, printKey, keySeparator, attributeSeparator, wrap)
}
/** @param {IEntity} other */
equals(other) {
if (other instanceof MirroredEntity) {
other = other.getter?.()
}
return this.getter?.().equals(other)
}
/** @returns {InstanceType<T>} */
valueOf(arg) {
// @ts-expect-error
return this.getter(arg).valueOf()
}
toString() {
return this.getter().toString()
this.toString = this.getter.toString.bind(this.getter())
return this.toString(insideString, indentation, Self, printKey, wrap)
}
}

View File

@@ -4,18 +4,15 @@ import IntegerEntity from "./IntegerEntity.js"
export default class NaturalNumberEntity extends IntegerEntity {
static grammar = this.createGrammar()
static grammar = /** @type {P<NaturalNumberEntity>} */(
P.numberNatural.map(v => new this(v))
)
get value() {
return super.value
}
set value(value) {
value = Math.round(Utility.clamp(value, 0))
value = Math.round(Utility.clamp(this.value, 0))
super.value = value
}
/** @returns {P<NaturalNumberEntity>} */
static createGrammar() {
return P.numberNatural.map(v => new this(v))
}
}

22
js/entity/NullEntity.js Executable file → Normal file
View File

@@ -3,25 +3,13 @@ import IEntity from "./IEntity.js"
export default class NullEntity extends IEntity {
static grammar = this.createGrammar()
/** @returns {P<NullEntity>} */
static createGrammar() {
static grammar = /** @type {P<NullEntity>} */(
// @ts-expect-error
return P.reg(new RegExp(String.raw`\(${P.whitespaceInlineOpt.getParser().regexp.source}\)`))
P.reg(new RegExp(String.raw`\(${P.whitespaceInlineOpt.getParser().regexp.source}\)`))
.map(v => new this())
.label("NullEntity")
}
)
serialize(
insideString = false,
indentation = "",
Self = /** @type {typeof IEntity} */(this.constructor)
) {
let result = "()"
if (Self.serialized) {
result = `"${result}"`
}
return result
toString() {
return "()"
}
}

View File

@@ -1,18 +1,23 @@
import P from "parsernostrum"
import Grammar from "../serialization/Grammar.js"
import IEntity from "./IEntity.js"
import Utility from "../Utility.js"
export default class NumberEntity extends IEntity {
static numberRegexSource = String.raw`${Grammar.numberRegexSource}(?<=(?:\.(\d*0+))?)`
static grammar = this.createGrammar()
/** @type {Number} */
static precision // Can override this.precision
static grammar = /** @type {P<NumberEntity>} */(
P.regArray(
new RegExp(`(?<n>${this.numberRegexSource})|(?<posInf>\\+?inf)|(?<negInf>-inf)`)
).map(({ 2: precision, groups: { n, posInf, negInf } }) => new this(
n ? Number(n) : posInf ? Number.POSITIVE_INFINITY : Number.NEGATIVE_INFINITY,
precision?.length
)
).label("NumberEntity")
)
#precision
#precision = 0
get precision() {
return /** @type {typeof NumberEntity} */(this.constructor).precision ?? this.#precision
return this.#precision
}
set precision(value) {
this.#precision = value
@@ -33,63 +38,10 @@ export default class NumberEntity extends IEntity {
this._value = value
}
constructor(value = 0, precision = null) {
constructor(value = 0, precision = 0) {
super()
this.value = Number(value)
if (precision !== null) {
this.#precision = Number(precision)
}
}
/** @returns {P<NumberEntity>} */
static createGrammar() {
return P.regArray(
new RegExp(`(?<n>${this.numberRegexSource})|(?<posInf>\\+?inf)|(?<negInf>-inf)`)
).map(({ 2: precision, groups: { n, posInf, negInf } }) => new this(
n ? Number(n) : posInf ? Number.POSITIVE_INFINITY : Number.NEGATIVE_INFINITY,
precision?.length
)
).label("NumberEntity")
}
/**
* @template {typeof NumberEntity} T
* @this {T}
* @returns {T}
*/
static withPrecision(value = 0) {
const result = this.asUniqueClass()
result.precision = value
return result
}
/** @param {Number} num */
static printNumber(num) {
if (num == Number.POSITIVE_INFINITY) {
return "inf"
} else if (num == Number.NEGATIVE_INFINITY) {
return "-inf"
}
return Utility.minDecimals(num)
}
serialize(
insideString = false,
indentation = "",
Self = /** @type {typeof NumberEntity} */(this.constructor),
) {
if (this.value === Number.POSITIVE_INFINITY) {
return "+inf"
}
if (this.value === Number.NEGATIVE_INFINITY) {
return "-inf"
}
const precision = Self.precision ?? this.precision
let result = precision !== undefined ? this.value.toFixed(precision) : this.value.toString()
if (Self.serialized) {
result = `"${result}"`
}
return result
this.#precision = Number(precision)
}
valueOf() {
@@ -97,6 +49,12 @@ export default class NumberEntity extends IEntity {
}
toString() {
return this.value.toString()
if (this.value === Number.POSITIVE_INFINITY) {
return "+inf"
}
if (this.value === Number.NEGATIVE_INFINITY) {
return "-inf"
}
return this.#precision ? this.value.toFixed(this.#precision) : this.value.toString()
}
}

View File

@@ -1,10 +1,10 @@
import P from "parsernostrum"
import Configuration from "../Configuration.js"
import Utility from "../Utility.js"
import nodeColor from "../decoding/nodeColor.js"
import nodeIcon from "../decoding/nodeIcon.js"
import nodeVariadic from "../decoding/nodeVariadic.js"
import Grammar from "../serialization/Grammar.js"
import Utility from "../Utility.js"
import AlternativesEntity from "./AlternativesEntity.js"
import ArrayEntity from "./ArrayEntity.js"
import BooleanEntity from "./BooleanEntity.js"
@@ -17,7 +17,6 @@ import MacroGraphReferenceEntity from "./MacroGraphReferenceEntity.js"
import MirroredEntity from "./MirroredEntity.js"
import NaturalNumberEntity from "./NaturalNumberEntity.js"
import NullEntity from "./NullEntity.js"
import NumberEntity from "./NumberEntity.js"
import ObjectReferenceEntity from "./ObjectReferenceEntity.js"
import PinEntity from "./PinEntity.js"
import ScriptVariableEntity from "./ScriptVariableEntity.js"
@@ -28,6 +27,8 @@ import VariableReferenceEntity from "./VariableReferenceEntity.js"
export default class ObjectEntity extends IEntity {
static trailing = true
#exported = false
get exported() {
return this.#exported
@@ -36,23 +37,15 @@ export default class ObjectEntity extends IEntity {
this.#exported = value
}
static #nameRegex = /^(\w+?)(?:_(\d+))?$/
/** @type {(k: String) => String} */
static printKey = k => !k.startsWith(Configuration.subObjectAttributeNamePrefix) ? k : ""
static attributeSeparator = "\n"
static wrap = this.notWrapped
static trailing = true
static attributes = {
...super.attributes,
Class: ObjectReferenceEntity,
Name: StringEntity,
Archetype: ObjectReferenceEntity,
ExportPath: MirroredEntity.of(ObjectReferenceEntity),
ExportPath: ObjectReferenceEntity,
ObjectRef: ObjectReferenceEntity,
BlueprintElementType: ObjectReferenceEntity,
BlueprintElementInstance: ObjectReferenceEntity,
ConstA: MirroredEntity.of(NumberEntity),
ConstB: MirroredEntity.of(NumberEntity),
PinTags: ArrayEntity.of(NullEntity).flagInlined(),
PinNames: ArrayEntity.of(StringEntity).flagInlined(),
AxisKey: SymbolEntity,
@@ -63,7 +56,6 @@ export default class ObjectEntity extends IEntity {
bIsPureFunc: BooleanEntity,
bIsConstFunc: BooleanEntity,
bIsCaseSensitive: BooleanEntity,
bDefaultsToPureFunc: BooleanEntity,
VariableReference: VariableReferenceEntity,
SelfContextInfo: SymbolEntity,
DelegatePropertyName: StringEntity,
@@ -108,11 +100,8 @@ export default class ObjectEntity extends IEntity {
SizeX: MirroredEntity.of(IntegerEntity),
SizeY: MirroredEntity.of(IntegerEntity),
Text: MirroredEntity.of(StringEntity),
ParameterName: StringEntity,
ExpressionGUID: GuidEntity,
MaterialExpressionEditorX: MirroredEntity.of(IntegerEntity),
MaterialExpressionEditorY: MirroredEntity.of(IntegerEntity),
MaterialExpressionGuid: GuidEntity,
NodeTitle: StringEntity,
NodeTitleColor: LinearColorEntity,
PositionX: MirroredEntity.of(IntegerEntity),
@@ -140,14 +129,12 @@ export default class ObjectEntity extends IEntity {
NodeGuid: GuidEntity,
ErrorType: IntegerEntity,
ErrorMsg: StringEntity,
ScriptVariables: ArrayEntity.flagInlined().of(ScriptVariableEntity),
ScriptVariables: ArrayEntity.of(ScriptVariableEntity),
Node: MirroredEntity.of(ObjectReferenceEntity),
ExportedNodes: StringEntity,
CustomProperties: ArrayEntity
.of(AlternativesEntity.accepting(PinEntity, UnknownPinEntity))
.withDefault()
.flagSilent(),
CustomProperties: ArrayEntity.of(AlternativesEntity.accepting(PinEntity, UnknownPinEntity)).withDefault().flagSilent(),
}
static #nameRegex = /^(\w+?)(?:_(\d+))?$/
static customPropertyGrammar = P.seq(
P.reg(/CustomProperties\s+/),
this.attributes.CustomProperties.type.grammar,
@@ -161,29 +148,55 @@ export default class ObjectEntity extends IEntity {
Grammar.symbolQuoted.map(v => [v, true]),
Grammar.symbol.map(v => [v, false]),
),
P.reg(new RegExp(String.raw`\s*\(\s*(\d+)\s*\)\s*\=\s*`), 1).map(Number) // Number in parentheses then equal
).chain(
/** @param {[[keyof ObjectEntity.attributes, Boolean], Number]} param */
([[symbol, quoted], index]) =>
(this.attributes[symbol]?.grammar ?? IEntity.unknownEntityGrammar).map(currentValue =>
values => {
if (values[symbol] === undefined) {
let arrayEntity = ArrayEntity
if (quoted != arrayEntity.quoted) {
arrayEntity = arrayEntity.flagQuoted(quoted)
}
if (!arrayEntity.inlined) {
arrayEntity = arrayEntity.flagInlined()
}
values[symbol] = new arrayEntity()
}
/** @type {ArrayEntity} */
const target = values[symbol]
target.values[index] = currentValue
}
)
P.reg(new RegExp(String.raw`\s*\(\s*(\d+)\s*\)\s*\=\s*`), 1).map(Number)
)
.chain(
/** @param {[[keyof ObjectEntity.attributes, Boolean], Number]} param */
([[symbol, quoted], index]) =>
this.attributes[symbol].grammar.map(currentValue =>
values => {
if (values[symbol] === undefined) {
let arrayEntity = ArrayEntity
if (quoted != arrayEntity.quoted) {
arrayEntity = arrayEntity.flagQuoted(quoted)
}
if (!arrayEntity.inlined) {
arrayEntity = arrayEntity.flagInlined()
}
values[symbol] = new arrayEntity()
}
/** @type {ArrayEntity} */
const target = values[symbol]
target.values[index] = currentValue
}
)
)
static grammar = /** @type {P<ObjectEntity>} */(
P.seq(
P.reg(/Begin +Object/),
P.seq(
P.whitespace,
P.alt(
this.createSubObjectGrammar(),
this.customPropertyGrammar,
Grammar.createAttributeGrammar(this, P.reg(Grammar.Regex.MultipleWordsSymbols)),
Grammar.createAttributeGrammar(this, Grammar.attributeNameQuoted, undefined, (obj, k, v) =>
Utility.objectSet(obj, ["attributes", ...k, "quoted"], true)
),
this.inlinedArrayEntryGrammar,
)
)
.map(([_0, entry]) => entry)
.many(),
P.reg(/\s+End +Object/),
)
.map(([_0, attributes, _2]) => {
const values = {}
attributes.forEach(attributeSetter => attributeSetter(values))
return new this(values)
})
.label("ObjectEntity")
)
static grammar = this.createGrammar()
static grammarMultipleObjects = P.seq(
P.whitespaceOpt,
this.grammar,
@@ -196,6 +209,16 @@ export default class ObjectEntity extends IEntity {
P.whitespaceOpt
).map(([_0, first, remaining, _4]) => [first, ...remaining])
static createSubObjectGrammar() {
return P.lazy(() => this.grammar)
.map(object =>
values => values[Configuration.subObjectAttributeNameFromEntity(object)] = object
)
}
/** @type {String} */
#class
constructor(values = {}) {
if (("NodePosX" in values) !== ("NodePosY" in values)) {
const entries = Object.entries(values)
@@ -208,21 +231,18 @@ export default class ObjectEntity extends IEntity {
super(values)
// Attributes
/** @type {ArrayEntity<typeof PinEntity | typeof UnknownPinEntity>} */ this.CustomProperties
/** @type {InstanceType<typeof ObjectEntity.attributes.AddedPins>} */ this.AddedPins
/** @type {InstanceType<typeof ObjectEntity.attributes.AdvancedPinDisplay>} */ this.AdvancedPinDisplay
/** @type {InstanceType<typeof ObjectEntity.attributes.Archetype>} */ this.Archetype
/** @type {InstanceType<typeof ObjectEntity.attributes.AxisKey>} */ this.AxisKey
/** @type {InstanceType<typeof ObjectEntity.attributes.bIsPureFunc>} */ this.bIsPureFunc
/** @type {InstanceType<typeof ObjectEntity.attributes.bDefaultsToPureFunc>} */ this.bDefaultsToPureFunc
/** @type {InstanceType<typeof ObjectEntity.attributes.BlueprintElementInstance>} */ this.BlueprintElementInstance
/** @type {InstanceType<typeof ObjectEntity.attributes.BlueprintElementType>} */ this.BlueprintElementType
/** @type {InstanceType<typeof ObjectEntity.attributes.Class>} */ this.Class
/** @type {InstanceType<typeof ObjectEntity.attributes.CommentColor>} */ this.CommentColor
/** @type {InstanceType<typeof ObjectEntity.attributes.ComponentPropertyName>} */ this.ComponentPropertyName
/** @type {InstanceType<typeof ObjectEntity.attributes.ConstA>} */ this.ConstA
/** @type {InstanceType<typeof ObjectEntity.attributes.ConstB>} */ this.ConstB
/** @type {InstanceType<typeof ObjectEntity.attributes.CustomFunctionName>} */ this.CustomFunctionName
/** @type {InstanceType<typeof ObjectEntity.attributes.CustomProperties>} */ this.CustomProperties
/** @type {InstanceType<typeof ObjectEntity.attributes.DelegatePropertyName>} */ this.DelegatePropertyName
/** @type {InstanceType<typeof ObjectEntity.attributes.DelegateReference>} */ this.DelegateReference
/** @type {InstanceType<typeof ObjectEntity.attributes.EnabledState>} */ this.EnabledState
@@ -261,10 +281,9 @@ export default class ObjectEntity extends IEntity {
/** @type {InstanceType<typeof ObjectEntity.attributes.Operation>} */ this.Operation
/** @type {InstanceType<typeof ObjectEntity.attributes.OpName>} */ this.OpName
/** @type {InstanceType<typeof ObjectEntity.attributes.OutputPins>} */ this.OutputPins
/** @type {InstanceType<typeof ObjectEntity.attributes.ParameterName>} */ this.ParameterName
/** @type {InstanceType<typeof ObjectEntity.attributes.PCGNode>} */ this.PCGNode
/** @type {InstanceType<typeof ObjectEntity.attributes.PinNames>} */ this.PinNames
/** @type {InstanceType<typeof ObjectEntity.attributes.PinTags>} */ this.PinTags
/** @type {InstanceType<typeof ObjectEntity.attributes.PinNames>} */ this.PinNames
/** @type {InstanceType<typeof ObjectEntity.attributes.PositionX>} */ this.PositionX
/** @type {InstanceType<typeof ObjectEntity.attributes.PositionY>} */ this.PositionY
/** @type {InstanceType<typeof ObjectEntity.attributes.ProxyFactoryFunctionName>} */ this.ProxyFactoryFunctionName
@@ -280,8 +299,8 @@ export default class ObjectEntity extends IEntity {
/** @type {InstanceType<typeof ObjectEntity.attributes.VariableReference>} */ this.VariableReference
// Legacy nodes pins
if (this["Pins"] instanceof ArrayEntity) {
this["Pins"].valueOf().forEach(
if (this["Pins"] instanceof Array) {
this["Pins"].forEach(
/** @param {ObjectReferenceEntity} objectReference */
objectReference => {
const pinObject = this[Configuration.subObjectAttributeNameFromReference(objectReference, true)]
@@ -289,7 +308,7 @@ export default class ObjectEntity extends IEntity {
const pinEntity = PinEntity.fromLegacyObject(pinObject)
pinEntity.LinkedTo = new (PinEntity.attributes.LinkedTo)()
this.getCustomproperties(true).push(pinEntity)
this.CustomProperties.ignored = true
Utility.objectSet(this, ["attributes", "CustomProperties", "ignored"], true)
}
}
)
@@ -304,37 +323,16 @@ export default class ObjectEntity extends IEntity {
obj.MaterialExpressionEditorX && (obj.MaterialExpressionEditorX.getter = () => this.NodePosX)
obj.MaterialExpressionEditorY && (obj.MaterialExpressionEditorY.getter = () => this.NodePosY)
if (this.getType() === Configuration.paths.materialExpressionComponentMask) {
const rgbaPins = Configuration.rgba.map(pinName => {
const result = this.getPinEntities().find(pin => pin.PinName.toString() === pinName)
result.recomputesNodeTitleOnChange = true
return result
})
// Reorder keys so that the added ones stay first
obj.keys = [...Configuration.rgba, ...obj.keys]
// The following attributes are too generic therefore not assigned a MirroredEntity
const rgbaPins = Configuration.rgba.map(pinName =>
this.getPinEntities().find(pin => pin.PinName.toString() === pinName && (pin.recomputesNodeTitleOnChange = true))
)
const silentBool = MirroredEntity.of(BooleanEntity).withDefault().flagSilent()
obj["R"] = new silentBool(() => rgbaPins[0].DefaultValue)
obj["G"] = new silentBool(() => rgbaPins[1].DefaultValue)
obj["B"] = new silentBool(() => rgbaPins[2].DefaultValue)
obj["A"] = new silentBool(() => rgbaPins[3].DefaultValue)
} else if (this.getType() === Configuration.paths.materialExpressionSubtract) {
const silentNumber = MirroredEntity
.of(NumberEntity.withPrecision(6))
.withDefault(() => new MirroredEntity(() => new NumberEntity(1)))
.flagSilent()
const pinA = this.getCustomproperties().find(pin => pin.PinName?.toString() === "A")
const pinB = this.getCustomproperties().find(pin => pin.PinName?.toString() === "B")
if (pinA || pinB) {
// Reorder keys so that the added ones stay first
obj.keys = ["ConstA", "ConstB", ...obj.keys]
if (pinA) {
pinA.recomputesNodeTitleOnChange = true
obj.ConstA = new silentNumber(() => pinA.DefaultValue)
}
if (pinB) {
pinB.recomputesNodeTitleOnChange = true
obj.ConstB = new silentNumber(() => pinB.DefaultValue)
}
}
obj.keys = [...Configuration.rgba, ...super.keys.filter(k => !Configuration.rgba.includes(k))]
}
}
/** @type {ObjectEntity} */
@@ -354,7 +352,6 @@ export default class ObjectEntity extends IEntity {
obj.Node.getter = () => new ObjectReferenceEntity(
this.PCGNode.type,
`${this.Name}.${this.PCGNode.path}`,
nodeRef.full,
)
}
}
@@ -372,92 +369,12 @@ export default class ObjectEntity extends IEntity {
? outputIndex++
: i
})
this.mirrorNameInExportPaths()
}
/** @returns {P<ObjectEntity>} */
static createGrammar() {
return P.seq(
P.reg(/Begin +Object/),
P.seq(
P.whitespace,
P.alt(
this.createSubObjectGrammar(),
this.customPropertyGrammar,
Grammar.createAttributeGrammar(this, P.reg(Grammar.Regex.MultipleWordsSymbols)),
Grammar.createAttributeGrammar(
this,
Grammar.attributeNameQuoted,
undefined,
(values, attributeKey, attributeValue) => {
Utility.objectSet(values, [...attributeKey, "quoted"], true)
},
),
this.inlinedArrayEntryGrammar,
)
)
.map(([_0, entry]) => entry)
.many(),
P.reg(/\s+End +Object/),
)
.map(([_0, attributes, _2]) => {
const values = {}
attributes.forEach(attributeSetter => attributeSetter(values))
return new this(values)
})
.label("ObjectEntity")
}
static createSubObjectGrammar() {
return P.lazy(() => this.grammar)
.map(object =>
values => {
object.trailing = false
values[Configuration.subObjectAttributeNameFromEntity(object)] = object
}
)
}
/**
* @protected
* Mirror then name part of the objects contained in this one in ExportPath
*/
mirrorNameInExportPaths(originalName = this.Name?.toString()) {
if (!originalName) {
return
}
const values = [this]
for (let i = 0; i < values.length; ++i) {
const value = values[i]
if (value instanceof ObjectEntity) {
values.push(...Object.values(value))
if (!value.ExportPath?.valueOf().path.includes(originalName)) {
continue
}
} else {
continue
}
const mirroredEntity = /** @type {typeof ObjectEntity} */(value.constructor).attributes.ExportPath
let originalExportPath = value.ExportPath
value.ExportPath = new mirroredEntity(
() => {
const exportPath = originalExportPath.valueOf()
return new (mirroredEntity.type)(
exportPath.type,
exportPath.path.replace(originalName, this.Name?.toString() ?? ""),
exportPath.full
)
}
)
}
}
/** @type {String} */
#class
getClass() {
if (!this.#class) {
this.#class = (this.Class?.path ? this.Class.path : this.Class?.type)
?? this.ExportPath?.valueOf()?.type
?? (this.ExportPath?.path ? this.ExportPath.path : this.ExportPath?.type)
?? ""
if (this.#class && !this.#class.startsWith("/")) {
// Old path names did not start with /Script or /Engine, check tests/resources/LegacyNodes.js
@@ -471,21 +388,21 @@ export default class ObjectEntity extends IEntity {
}
getType() {
const path = this.MacroGraphReference?.MacroGraph?.path
if (path) {
return path
let classValue = this.getClass()
if (this.MacroGraphReference?.MacroGraph?.path) {
return this.MacroGraphReference.MacroGraph.path
}
if (this.MaterialExpression) {
return this.MaterialExpression.type
}
return this.getClass()
return classValue
}
getObjectName(dropCounter = false) {
if (dropCounter) {
return this.getNameAndCounter()[0]
}
return this.Name.toString()
return this.Name.valueOf()
}
/** @returns {[String, Number]} */
@@ -598,10 +515,24 @@ export default class ObjectEntity extends IEntity {
}
isMaterial() {
const classValue = this.getClass()
return classValue.startsWith("/Script/Engine.MaterialExpression")
|| classValue.startsWith("/Script/InterchangeImport.MaterialExpression")
|| classValue.startsWith("/Script/UnrealEd.MaterialGraph")
return this.getClass() === Configuration.paths.materialGraphNode
// return [
// Configuration.paths.materialExpressionConstant,
// Configuration.paths.materialExpressionConstant2Vector,
// Configuration.paths.materialExpressionConstant3Vector,
// Configuration.paths.materialExpressionConstant4Vector,
// Configuration.paths.materialExpressionLogarithm,
// Configuration.paths.materialExpressionLogarithm10,
// Configuration.paths.materialExpressionLogarithm2,
// Configuration.paths.materialExpressionMaterialFunctionCall,
// Configuration.paths.materialExpressionSquareRoot,
// Configuration.paths.materialExpressionTextureCoordinate,
// Configuration.paths.materialExpressionTextureSample,
// Configuration.paths.materialGraphNode,
// Configuration.paths.materialGraphNodeComment,
// ]
// .includes(this.getClass())
}
/** @return {ObjectEntity} */
@@ -614,7 +545,7 @@ export default class ObjectEntity extends IEntity {
isPcg() {
return this.getClass() === Configuration.paths.pcgEditorGraphNode
|| this.getPcgSubobject() != null
|| this.getPcgSubobject()
}
isNiagara() {
@@ -685,62 +616,31 @@ export default class ObjectEntity extends IEntity {
return super.showProperty(key)
}
/** @param {typeof ObjectEntity} Self */
doSerialize(
toString(
insideString = false,
indentation = "",
Self = /** @type {typeof ObjectEntity} */(this.constructor),
Self = this.Self(),
printKey = Self.printKey,
keySeparator = Self.keySeparator,
attributeSeparator = Self.attributeSeparator,
wrap = Self.wrap,
) {
const isSelfOverriden = Self !== this.constructor
const deeperIndentation = indentation + Configuration.indentation
const initial_trailing = this.trailing
this.trailing = true
const content = super.doSerialize(insideString, deeperIndentation, Self, printKey, keySeparator, attributeSeparator, wrap)
this.trailing = initial_trailing
const moreIndentation = indentation + Configuration.indentation
let result = indentation + "Begin Object"
+ ((this.Class?.type || this.Class?.path)
// && Self.attributes.Class.ignored !== true
// && this.Class.ignored !== true
? ` Class${keySeparator}${this.Class.serialize(insideString)}`
: ""
)
+ (this.Name
// && Self.attributes.Name.ignored !== true
// && this.Name.ignored !== true
? ` Name${keySeparator}${this.Name.serialize(insideString)}`
: ""
)
+ (this.Archetype
// && Self.attributes.Archetype.ignored !== true
// && this.Archetype.ignored !== true
? ` Archetype${keySeparator}${this.Archetype.serialize(insideString)}`
: ""
)
+ ((this.ExportPath?.valueOf()?.type || this.ExportPath?.valueOf()?.path)
// && Self.attributes.ExportPath.valueOf().ignored !== true
// && this.ExportPath.valueOf().ignored !== true
? ` ExportPath${keySeparator}${this.ExportPath.serialize(insideString)}`
: ""
)
+ attributeSeparator
+ content
+ (Self.attributes.CustomProperties.ignored !== true && this.CustomProperties.ignored !== true
? this.getCustomproperties()
.map(pin =>
deeperIndentation
+ printKey("CustomProperties ")
+ pin.serialize(insideString)
+ attributeSeparator
)
.join("")
+ (this.Class?.type || this.Class?.path ? ` Class=${this.Class.toString(insideString)}` : "")
+ (this.Name ? ` Name=${this.Name.toString(insideString)}` : "")
+ (this.Archetype ? ` Archetype=${this.Archetype.toString(insideString)}` : "")
+ (this.ExportPath?.type || this.ExportPath?.path ? ` ExportPath=${this.ExportPath.toString(insideString)}` : "")
+ "\n"
+ super.toString(insideString, moreIndentation, Self, printKey, wrap)
+ (!this.CustomProperties.Self().ignored
? this.getCustomproperties().map(pin =>
moreIndentation
+ printKey("CustomProperties ")
+ pin.toString(insideString)
+ this.Self().attributeSeparator
).join("")
: ""
)
+ indentation + "End Object"
+ (isSelfOverriden && Self.trailing || this.trailing ? attributeSeparator : "")
return result
}
}

View File

@@ -5,12 +5,37 @@ import IEntity from "./IEntity.js"
export default class ObjectReferenceEntity extends IEntity {
static #quotedParser = P.regArray(new RegExp(
`'"(${Grammar.Regex.InsideString.source})"'`
+ "|"
+ `'(${Grammar.Regex.InsideSingleQuotedString.source})'`
)).map(([_0, a, b]) => a ?? b)
static typeReference = P.reg(
// @ts-expect-error
new RegExp(Grammar.Regex.Path.source + "|" + Grammar.symbol.getParser().regexp.source)
)
static fullReferenceGrammar = this.createFullReferenceGrammar()
static grammar = this.createGrammar()
static fullReferenceGrammar = P.regArray(
new RegExp(
// @ts-expect-error
"(" + this.typeReference.getParser().regexp.source + ")"
// @ts-expect-error
+ "(?:" + this.#quotedParser.getParser().parser.regexp.source + ")"
)
).map(([full, type, ...path]) => new this(type, path.find(v => v), full))
static fullReferenceSerializedGrammar = P.regArray(
new RegExp(
'"(' + Grammar.Regex.InsideString.source + "?)"
+ "(?:'(" + Grammar.Regex.InsideSingleQuotedString.source + `?)')?"`
)
).map(([full, type, path]) => new this(type, path, full))
static typeReferenceGrammar = this.typeReference.map(v => new this(v, "", v))
static grammar = /** @type {P<ObjectReferenceEntity>} */(
P.alt(
this.fullReferenceSerializedGrammar,
this.fullReferenceGrammar,
this.typeReferenceGrammar,
).label("ObjectReferenceEntity")
)
#type
get type() {
@@ -25,100 +50,33 @@ export default class ObjectReferenceEntity extends IEntity {
return this.#path
}
set path(value) {
this.#name = ""
this.#path = value
}
#serializer
#fullEscaped
/** @type {String} */
#full
get full() {
return this.#serializer
return this.#full
}
set full(value) {
this.#serializer = value
this.#full = value
}
#name = ""
/** @param {(t: String, p: String) => String} serializer */
constructor(
type = "None",
path = "",
serializer = type.includes("/") || path
? (t, p) => `"${t + (p ? (`'${p}'`) : "")}"`
: (t, p) => t) {
constructor(type = "None", path = "", full = null) {
super()
this.#type = type
this.#path = path
this.#serializer = serializer
}
/** @returns {P<ObjectReferenceEntity>} */
static createGrammar() {
return P.alt(
this.createFullReferenceSerializedGrammar(),
this.createFullReferenceGrammar(),
this.createTypeReferenceGrammar(),
).label("ObjectReferenceEntity")
}
/** @returns {P<ObjectReferenceEntity>} */
static createFullReferenceGrammar() {
return P.regArray(
new RegExp(
// @ts-expect-error
"(" + this.typeReference.getParser().regexp.source + ")"
+ "(?:"
+ `'"(${Grammar.Regex.InsideString.source})"'`
+ "|"
+ `'(${Grammar.Regex.InsideSingleQuotedString.source})'`
+ ")"
)
).map(([full, type, fullQuotedPath, simpleQuotedPath]) => {
let fullQuoted = fullQuotedPath ? true : false
let quotes = fullQuoted ? [`'"`, `"'`] : ["'", "'"]
return new this(
type,
fullQuoted ? fullQuotedPath : simpleQuotedPath,
(t, p) => t + quotes[0] + p + quotes[1]
)
})
}
/** @returns {P<ObjectReferenceEntity>} */
static createFullReferenceSerializedGrammar() {
return P.regArray(
new RegExp(
'"(' + Grammar.Regex.InsideString.source + "?)"
+ "(?:'(" + Grammar.Regex.InsideSingleQuotedString.source + `?)')?"`
)
).map(([_0, type, path]) => new this(type, path, (t, p) => `"${t}${p ? `'${p}'` : ""}"`))
}
/** @returns {P<ObjectReferenceEntity>} */
static createTypeReferenceGrammar() {
return this.typeReference.map(v => new this(v, "", (t, p) => t))
this.#full = full ?? `"${this.type + (this.path ? (`'${this.path}'`) : "")}"`
}
static createNoneInstance() {
return new this("None")
return new ObjectReferenceEntity("None")
}
getName(dropCounter = false) {
if (!this.#name) {
if (!dropCounter) {
return this.#name = Utility.getNameFromPath(this.path.replace(/_C$/, ""), dropCounter)
}
return Utility.getNameFromPath(this.path.replace(/_C$/, ""), dropCounter)
}
return this.#name
}
doSerialize(insideString = false) {
let result = this.full(this.type, this.path)
if (insideString) {
result = Utility.escapeString(result, false)
}
return result
return Utility.getNameFromPath(this.path.replace(/_C$/, ""), dropCounter)
}
/** @param {IEntity} other */
@@ -129,7 +87,19 @@ export default class ObjectReferenceEntity extends IEntity {
return this.type == other.type && this.path == other.path
}
toString() {
return this.full(this.type, this.path)
toString(
insideString = false,
indentation = "",
Self = this.Self(),
printKey = Self.printKey,
wrap = Self.wrap,
) {
if (insideString) {
if (this.#fullEscaped === undefined) {
this.#fullEscaped = Utility.escapeString(this.#full, false)
}
return this.#fullEscaped
}
return this.full
}
}

View File

@@ -29,7 +29,6 @@ import SimpleSerializationVector2DEntity from "./SimpleSerializationVector2DEnti
import SimpleSerializationVector4DEntity from "./SimpleSerializationVector4DEntity.js"
import SimpleSerializationVectorEntity from "./SimpleSerializationVectorEntity.js"
import StringEntity from "./StringEntity.js"
import SymbolEntity from "./SymbolEntity.js"
import Vector2DEntity from "./Vector2DEntity.js"
import Vector4DEntity from "./Vector4DEntity.js"
import VectorEntity from "./VectorEntity.js"
@@ -39,29 +38,24 @@ export default class PinEntity extends IEntity {
static lookbehind = "Pin"
static #typeEntityMap = {
[Configuration.paths.linearColor]: LinearColorEntity,
[Configuration.paths.rotator]: RotatorEntity,
[Configuration.paths.vector]: VectorEntity,
[Configuration.paths.vector2D]: Vector2DEntity,
[Configuration.paths.vector4f]: Vector4DEntity,
"bool": BooleanEntity,
"byte": ByteEntity,
"enum": EnumEntity,
"exec": StringEntity,
"float": NumberEntity,
"int": IntegerEntity,
"int64": Integer64Entity,
"name": StringEntity,
"real": NumberEntity,
"string": StringEntity,
[Configuration.paths.linearColor]: LinearColorEntity,
[Configuration.paths.niagaraBool]: BooleanEntity,
[Configuration.paths.niagaraFloat]: NumberEntity,
[Configuration.paths.niagaraPosition]: VectorEntity,
[Configuration.paths.rotator]: RotatorEntity,
[Configuration.paths.vector]: VectorEntity,
[Configuration.paths.vector2D]: Vector2DEntity,
[Configuration.paths.vector4f]: Vector4DEntity,
}
static #alternativeTypeEntityMap = {
"enum": EnumDisplayValueEntity,
"rg": RBSerializationVector2DEntity,
[Configuration.paths.niagaraPosition]: SimpleSerializationVectorEntity.flagAllowShortSerialization(),
[Configuration.paths.rotator]: SimpleSerializationRotatorEntity,
[Configuration.paths.vector]: SimpleSerializationVectorEntity,
[Configuration.paths.vector2D]: SimpleSerializationVector2DEntity,
@@ -86,19 +80,21 @@ export default class PinEntity extends IEntity {
DefaultValue:
ComputedTypeEntity.from(
/** @param {PinEntity} pinEntity */
pinEntity => pinEntity.getEntityType(true)?.flagSerialized() ?? StringEntity
),
pinEntity => pinEntity.getEntityType(true) ?? StringEntity
).flagSerialized(),
AutogeneratedDefaultValue: StringEntity,
DefaultObject: ObjectReferenceEntity,
PersistentGuid: GuidEntity,
bHidden: BooleanEntity,
bNotConnectable: BooleanEntity,
bDefaultValueIsReadOnly: BooleanEntity,
bDefaultValueIsIgnored: BooleanEntity,
bAdvancedView: BooleanEntity,
bOrphanedPin: BooleanEntity,
bHidden: BooleanEntity.withDefault(),
bNotConnectable: BooleanEntity.withDefault(),
bDefaultValueIsReadOnly: BooleanEntity.withDefault(),
bDefaultValueIsIgnored: BooleanEntity.withDefault(),
bAdvancedView: BooleanEntity.withDefault(),
bOrphanedPin: BooleanEntity.withDefault(),
}
static grammar = this.createGrammar()
static grammar = /** @type {P<PinEntity>} */(
Grammar.createEntityGrammar(this)
)
#recomputesNodeTitleOnChange = false
set recomputesNodeTitleOnChange(value) {
@@ -108,26 +104,9 @@ export default class PinEntity extends IEntity {
return this.#recomputesNodeTitleOnChange
}
/** @type {ObjectEntity} */
#objectEntity = null
#objectEntity
get objectEntity() {
try {
/*
* Why inside a try block ?
* It is because of this issue: https://stackoverflow.com/questions/61237153/access-private-method-in-an-overriden-method-called-from-the-base-class-construc
* super(values) will call IEntity constructor while this instance is not yet fully constructed
* IEntity will call computedEntity.compute(this) to initialize DefaultValue from this class
* Which in turn calls pinEntity.getEntityType(true)
* Which calls this.getType()
* Which calls this.objectEntity?.isPcg()
* Which would access #objectEntity through get objectEntity()
* And this would violate the private access rule (because this class is not yet constructed)
* If this issue in the future will be fixed in all the major browsers, please remove this try catch
*/
return this.#objectEntity
} catch (e) {
return null
}
return this.#objectEntity
}
set objectEntity(value) {
this.#objectEntity = value
@@ -160,12 +139,6 @@ export default class PinEntity extends IEntity {
/** @type {InstanceType<typeof PinEntity.attributes.bDefaultValueIsIgnored>} */ this.bDefaultValueIsIgnored
/** @type {InstanceType<typeof PinEntity.attributes.bAdvancedView>} */ this.bAdvancedView
/** @type {InstanceType<typeof PinEntity.attributes.bOrphanedPin>} */ this.bOrphanedPin
/** @type {ObjectEntity} */ this.objectEntity
}
/** @returns {P<PinEntity>} */
static createGrammar() {
return Grammar.createEntityGrammar(this)
}
/** @param {ObjectEntity} objectEntity */
@@ -173,10 +146,9 @@ export default class PinEntity extends IEntity {
return new PinEntity(objectEntity)
}
/** @returns {String} */
getType() {
const category = this.PinType.PinCategory?.toString().toLocaleLowerCase()
if (["struct", "class", "object", "type", "statictype"].includes(category)) {
const category = this.PinType.PinCategory?.valueOf().toLocaleLowerCase()
if (category === "struct" || category === "class" || category === "object" || category === "type") {
return this.PinType.PinSubCategoryObject?.path
}
if (this.isEnum()) {
@@ -185,12 +157,12 @@ export default class PinEntity extends IEntity {
if (this.objectEntity?.isPcg()) {
const pcgSuboject = this.objectEntity.getPcgSubobject()
const pinObjectReference = this.isInput()
? pcgSuboject.InputPins?.valueOf()[this.pinIndex]
: pcgSuboject.OutputPins?.valueOf()[this.pinIndex]
? pcgSuboject.InputPins?.[this.pinIndex]
: pcgSuboject.OutputPins?.[this.pinIndex]
if (pinObjectReference) {
/** @type {ObjectEntity} */
const pinObject = pcgSuboject[Configuration.subObjectAttributeNameFromReference(pinObjectReference, true)]
let allowedTypes = pinObject.Properties?.AllowedTypes?.toString() ?? ""
let allowedTypes = pinObject.Properties?.AllowedTypes?.valueOf() ?? ""
if (allowedTypes == "") {
allowedTypes = this.PinType.PinCategory ?? ""
if (allowedTypes == "") {
@@ -209,7 +181,7 @@ export default class PinEntity extends IEntity {
}
}
if (category === "optional") {
const subCategory = this.PinType.PinSubCategory?.toString()
const subCategory = this.PinType.PinSubCategory?.valueOf()
switch (subCategory) {
case "red":
return "real"
@@ -228,9 +200,9 @@ export default class PinEntity extends IEntity {
/** @returns {typeof IEntity} */
getEntityType(alternative = false) {
const type = this.getType()
const entity = PinEntity.#typeEntityMap[type]
const alternativeEntity = PinEntity.#alternativeTypeEntityMap[type]
const typeString = this.getType()
const entity = PinEntity.#typeEntityMap[typeString]
const alternativeEntity = PinEntity.#alternativeTypeEntityMap[typeString]
return alternative && alternativeEntity !== undefined
? alternativeEntity
: entity
@@ -247,21 +219,20 @@ export default class PinEntity extends IEntity {
getDefaultValue(maybeCreate = false) {
if (this.DefaultValue === undefined && maybeCreate) {
this.DefaultValue = /** @type {T} */(new (this.getEntityType(true))())
this.DefaultValue = new (this.getEntityType(true))()
}
return this.DefaultValue
}
isEnum() {
const type = this.PinType.PinSubCategoryObject?.type
const type = this.PinType.PinSubCategoryObject.type
return type === Configuration.paths.enum
|| type === Configuration.paths.userDefinedEnum
|| type?.toLowerCase() === "enum"
|| type.toLowerCase() === "enum"
}
isExecution() {
return this.PinType.PinCategory.toString() === "exec"
|| this.getType() === Configuration.paths.niagaraParameterMap
}
isHidden() {
@@ -269,15 +240,15 @@ export default class PinEntity extends IEntity {
}
isInput() {
return !this.isHidden() && this.Direction?.toString() != "EGPD_Output"
return !this.isHidden() && this.Direction.valueOf() != "EGPD_Output"
}
isOutput() {
return !this.isHidden() && this.Direction?.toString() == "EGPD_Output"
return !this.isHidden() && this.Direction.valueOf() == "EGPD_Output"
}
isLinked() {
return this.LinkedTo?.length > 0
return this.LinkedTo?.length > 0 ?? false
}
/**
@@ -288,10 +259,10 @@ export default class PinEntity extends IEntity {
linkTo(targetObjectName, targetPinEntity) {
const linkFound = this.LinkedTo.values?.some(pinReferenceEntity =>
pinReferenceEntity.objectName.toString() == targetObjectName
&& pinReferenceEntity.pinGuid.toString() == targetPinEntity.PinId.toString()
&& pinReferenceEntity.pinGuid.valueOf() == targetPinEntity.PinId.valueOf()
)
if (!linkFound) {
this.LinkedTo.values.push(new PinReferenceEntity(new SymbolEntity(targetObjectName), targetPinEntity.PinId))
this.LinkedTo.values.push(new PinReferenceEntity(targetObjectName, targetPinEntity.PinId,))
return true
}
return false // Already linked
@@ -305,12 +276,12 @@ export default class PinEntity extends IEntity {
unlinkFrom(targetObjectName, targetPinEntity) {
const indexElement = this.LinkedTo.values?.findIndex(pinReferenceEntity => {
return pinReferenceEntity.objectName.toString() == targetObjectName
&& pinReferenceEntity.pinGuid.toString() == targetPinEntity.PinId.toString()
&& pinReferenceEntity.pinGuid.valueOf() == targetPinEntity.PinId.valueOf()
})
if (indexElement >= 0) {
this.LinkedTo.values.splice(indexElement, 1)
if (this.LinkedTo.length === 0 && PinEntity.attributes.LinkedTo.default === undefined) {
this.LinkedTo.values = []
this.LinkedTo = undefined
}
return true
}
@@ -318,7 +289,7 @@ export default class PinEntity extends IEntity {
}
getSubCategory() {
return this.PinType.PinSubCategoryObject?.path
return this.PinType.PinSubCategoryObject.path
}
pinColor() {

View File

@@ -5,7 +5,15 @@ import SymbolEntity from "./SymbolEntity.js"
export default class PinReferenceEntity extends IEntity {
static grammar = this.createGrammar()
static grammar = /** @type {P<PinReferenceEntity>} */(
P.seq(
SymbolEntity.grammar,
P.whitespace,
GuidEntity.grammar
)
.map(([objectName, _1, pinGuid]) => new this(objectName, pinGuid))
.label("PinReferenceEntity")
)
/**
* @param {SymbolEntity} objectName
@@ -17,18 +25,7 @@ export default class PinReferenceEntity extends IEntity {
this.pinGuid = pinGuid
}
/** @returns {P<PinReferenceEntity>} */
static createGrammar() {
return P.seq(
SymbolEntity.grammar,
P.whitespace,
GuidEntity.grammar
)
.map(([objectName, _1, pinGuid]) => new this(objectName, pinGuid))
.label("PinReferenceEntity")
}
doSerialize() {
return this.objectName.serialize() + " " + this.pinGuid.serialize()
toString() {
return this.objectName.toString() + " " + this.pinGuid.toString()
}
}

View File

@@ -12,17 +12,19 @@ export default class PinTypeEntity extends IEntity {
static attributes = {
...super.attributes,
PinCategory: StringEntity.withDefault(),
PinSubCategory: StringEntity,
PinSubCategoryObject: ObjectReferenceEntity,
PinSubCategoryMemberReference: FunctionReferenceEntity,
PinSubCategory: StringEntity.withDefault(),
PinSubCategoryObject: ObjectReferenceEntity.withDefault(),
PinSubCategoryMemberReference: FunctionReferenceEntity.withDefault(),
ContainerType: SymbolEntity,
bIsReference: BooleanEntity,
bIsConst: BooleanEntity,
bIsWeakPointer: BooleanEntity,
bIsUObjectWrapper: BooleanEntity,
bSerializeAsSinglePrecisionFloat: BooleanEntity,
bIsReference: BooleanEntity.withDefault(),
bIsConst: BooleanEntity.withDefault(),
bIsWeakPointer: BooleanEntity.withDefault(),
bIsUObjectWrapper: BooleanEntity.withDefault(),
bSerializeAsSinglePrecisionFloat: BooleanEntity.withDefault(),
}
static grammar = this.createGrammar()
static grammar = /** @type {P<PinTypeEntity>} */(
Grammar.createEntityGrammar(this).label("PinTypeEntity")
)
constructor(values = {}) {
super(values)
@@ -39,11 +41,6 @@ export default class PinTypeEntity extends IEntity {
/** @type {InstanceType<typeof PinTypeEntity.attributes.bSerializeAsSinglePrecisionFloat>} */ this.bSerializeAsSinglePrecisionFloat
}
/** @returns {P<PinTypeEntity>} */
static createGrammar() {
return Grammar.createEntityGrammar(this).label("PinTypeEntity")
}
/** @param {PinTypeEntity} other */
copyTypeFrom(other) {
for (const key of this.keys) {

30
js/entity/RBSerializationVector2DEntity.js Executable file → Normal file
View File

@@ -4,23 +4,15 @@ import Vector2DEntity from "./Vector2DEntity.js"
export default class RBSerializationVector2DEntity extends Vector2DEntity {
static grammar = this.createGrammar()
/** @returns {P<RBSerializationVector2DEntity>} */
static createGrammar() {
return P.alt(
P.regArray(new RegExp(
/X\s*=\s*/.source + "(?<x>" + Grammar.numberRegexSource + ")"
+ "\\s+"
+ /Y\s*=\s*/.source + "(?<y>" + Grammar.numberRegexSource + ")"
)).map(({ groups: { x, y } }) => new this({
X: new (Vector2DEntity.attributes.X)(x),
Y: new (Vector2DEntity.attributes.Y)(y),
})),
Vector2DEntity.grammar.map(v => new this({
X: v.X,
Y: v.Y,
}))
).label("RBSerializationVector2DEntity")
}
static grammar = /** @type {P<RBSerializationVector2DEntity>} */(P.alt(
P.regArray(new RegExp(
/X\s*=\s*/.source + "(?<x>" + Grammar.numberRegexSource + ")"
+ "\\s+"
+ /Y\s*=\s*/.source + "(?<y>" + Grammar.numberRegexSource + ")"
)).map(({ groups: { x, y } }) => new this({
X: Number(x),
Y: Number(y),
})),
Vector2DEntity.grammar
).label("RBSerializationVector2DEntity"))
}

9
js/entity/RotatorEntity.js Executable file → Normal file
View File

@@ -11,7 +11,9 @@ export default class RotatorEntity extends IEntity {
P: NumberEntity.withDefault(),
Y: NumberEntity.withDefault(),
}
static grammar = this.createGrammar()
static grammar = /** @type {P<RotatorEntity>} */(
Grammar.createEntityGrammar(this, Grammar.commaSeparation, true).label("RotatorEntity")
)
constructor(values) {
super(values)
@@ -20,11 +22,6 @@ export default class RotatorEntity extends IEntity {
/** @type {InstanceType<typeof RotatorEntity.attributes.Y>} */ this.Y
}
/** @returns {P<RotatorEntity>} */
static createGrammar() {
return Grammar.createEntityGrammar(this, Grammar.commaSeparation, 1).label("RotatorEntity")
}
getRoll() {
return this.R
}

9
js/entity/ScriptVariableEntity.js Executable file → Normal file
View File

@@ -11,16 +11,13 @@ export default class ScriptVariableEntity extends IEntity {
ScriptVariable: ObjectReferenceEntity,
OriginalChangeId: GuidEntity,
}
static grammar = this.createGrammar()
static grammar = /** @type {P<ScriptVariableEntity>} */(
Grammar.createEntityGrammar(this).label("ScriptVariableEntity")
)
constructor(values = {}) {
super(values)
/** @type {InstanceType<typeof ScriptVariableEntity.attributes.ScriptVariable>} */ this.ScriptVariable
/** @type {InstanceType<typeof ScriptVariableEntity.attributes.OriginalChangeId>} */ this.OriginalChangeId
}
/** @returns {P<ScriptVariableEntity>} */
static createGrammar() {
return Grammar.createEntityGrammar(this).label("ScriptVariableEntity")
}
}

35
js/entity/SimpleSerializationRotatorEntity.js Executable file → Normal file
View File

@@ -1,15 +1,13 @@
import P from "parsernostrum"
import NumberEntity from "./NumberEntity.js"
import Grammar from "../serialization/Grammar.js"
import RotatorEntity from "./RotatorEntity.js"
import NumberEntity from "./NumberEntity.js"
export default class SimpleSerializationRotatorEntity extends RotatorEntity {
static attributeSeparator = ", "
static grammar = this.createGrammar()
/** @returns {P<SimpleSerializationRotatorEntity>} */
static createGrammar() {
return P.alt(
static grammar = /** @type {P<SimpleSerializationRotatorEntity>} */(
P.alt(
P.regArray(new RegExp(
`(${NumberEntity.numberRegexSource})`
+ String.raw`\s*,\s*`
@@ -17,9 +15,9 @@ export default class SimpleSerializationRotatorEntity extends RotatorEntity {
+ String.raw`\s*,\s*`
+ `(${NumberEntity.numberRegexSource})`
)).map(([_, p, pPrecision, y, yPrecision, r, rPrecision]) => new this({
R: new (RotatorEntity.attributes.R)(r, rPrecision?.length),
P: new (RotatorEntity.attributes.P)(p, pPrecision?.length),
Y: new (RotatorEntity.attributes.Y)(y, yPrecision?.length),
R: new NumberEntity(r, rPrecision?.length),
P: new NumberEntity(p, pPrecision?.length),
Y: new NumberEntity(y, yPrecision?.length),
})),
RotatorEntity.grammar.map(v => new this({
R: v.R,
@@ -27,14 +25,17 @@ export default class SimpleSerializationRotatorEntity extends RotatorEntity {
Y: v.Y,
}))
).label("SimpleSerializationRotatorEntity")
}
)
doSerialize() {
const attributeSeparator = /** @type {typeof SimpleSerializationRotatorEntity} */(
this.constructor
).attributeSeparator
return this.P.serialize() + attributeSeparator
+ this.Y.serialize() + attributeSeparator
+ this.R.serialize() + (this.trailing ? attributeSeparator : "")
toString(
insideString = false,
indentation = "",
Self = this.Self(),
printKey = Self.printKey,
wrap = Self.wrap,
) {
return this.P.toString() + Self.attributeSeparator
+ this.Y.toString() + Self.attributeSeparator
+ this.R.toString() + (this.trailing ? Self.attributeSeparator : "")
}
}

31
js/entity/SimpleSerializationVector2DEntity.js Executable file → Normal file
View File

@@ -1,35 +1,36 @@
import P from "parsernostrum"
import NumberEntity from "./NumberEntity.js"
import Grammar from "../serialization/Grammar.js"
import Vector2DEntity from "./Vector2DEntity.js"
import NumberEntity from "./NumberEntity.js"
export default class SimpleSerializationVector2DEntity extends Vector2DEntity {
static attributeSeparator = ", "
static grammar = this.createGrammar()
/** @returns {P<SimpleSerializationVector2DEntity>} */
static createGrammar() {
return P.alt(
static grammar = /** @type {P<SimpleSerializationVector2DEntity>} */(
P.alt(
P.regArray(new RegExp(
`(${NumberEntity.numberRegexSource})`
+ String.raw`\s*,\s*`
+ `(${NumberEntity.numberRegexSource})`
)).map(([_, x, xPrecision, y, yPrecision]) => new this({
X: new (Vector2DEntity.attributes.X)(x, xPrecision?.length),
Y: new (Vector2DEntity.attributes.Y)(y, yPrecision?.length),
X: new NumberEntity(x, xPrecision?.length),
Y: new NumberEntity(y, yPrecision?.length),
})),
Vector2DEntity.grammar.map(v => new this({
X: v.X,
Y: v.Y,
}))
).label("SimpleSerializationVector2DEntity")
}
)
doSerialize() {
const attributeSeparator = /** @type {typeof SimpleSerializationVector2DEntity} */(
this.constructor
).attributeSeparator
return this.X.serialize() + attributeSeparator
+ this.Y.serialize() + (this.trailing ? attributeSeparator : "")
toString(
insideString = false,
indentation = "",
Self = this.Self(),
printKey = Self.printKey,
wrap = Self.wrap,
) {
return this.X.toString() + Self.attributeSeparator
+ this.Y.toString() + (this.trailing ? Self.attributeSeparator : "")
}
}

11
js/entity/SimpleSerializationVector4DEntity.js Executable file → Normal file
View File

@@ -4,9 +4,8 @@ import Vector4DEntity from "./Vector4DEntity.js"
export default class SimpleSerializationVector4DEntity extends Vector4DEntity {
static grammar = this.createGrammar()
static grammar = /** @type {P<SimpleSerializationVector4DEntity> } */(this.createGrammar())
/** @returns {P<SimpleSerializationVector4DEntity> } */
static createGrammar() {
const number = Grammar.numberRegexSource
return P.alt(
@@ -20,10 +19,10 @@ export default class SimpleSerializationVector4DEntity extends Vector4DEntity {
+ `(${Grammar.numberRegexSource})`
))
.map(([_0, x, y, z, w]) => new this({
X: new (Vector4DEntity.attributes.X)(x),
Y: new (Vector4DEntity.attributes.Y)(y),
Z: new (Vector4DEntity.attributes.Z)(z),
W: new (Vector4DEntity.attributes.W)(w),
X: Number(x),
Y: Number(y),
Z: Number(z),
W: Number(w),
})),
Vector4DEntity.grammar
)

53
js/entity/SimpleSerializationVectorEntity.js Executable file → Normal file
View File

@@ -1,31 +1,24 @@
import P from "parsernostrum"
import NumberEntity from "./NumberEntity.js"
import Grammar from "../serialization/Grammar.js"
import VectorEntity from "./VectorEntity.js"
import NumberEntity from "./NumberEntity.js"
export default class SimpleSerializationVectorEntity extends VectorEntity {
static allowShortSerialization = false
static attributeSeparator = ", "
static grammar = this.createGrammar()
/** @returns {P<SimpleSerializationVectorEntity>} */
static createGrammar() {
return P.alt(
static grammar = /** @type {P<SimpleSerializationVectorEntity>} */(
P.alt(
P.regArray(new RegExp(
`(${NumberEntity.numberRegexSource})`
// If allow simple serialization then it can parse only a single number ...
+ (this.allowShortSerialization ? `(?:` : "")
+ String.raw`\s*,\s*`
+ `(${NumberEntity.numberRegexSource})`
+ String.raw`\s*,\s*`
+ `(${NumberEntity.numberRegexSource})`
// ... that will be assigned to X and the rest is optional and set to 0
+ (this.allowShortSerialization ? `)?` : "")
))
.map(([_, x, xPrecision, y, yPrecision, z, zPrecision]) => new this({
X: new (VectorEntity.attributes.X)(x, xPrecision?.length),
Y: new (VectorEntity.attributes.Y)(y, yPrecision?.length),
Z: new (VectorEntity.attributes.Z)(z, zPrecision?.length),
X: new NumberEntity(x, xPrecision?.length),
Y: new NumberEntity(y, yPrecision?.length),
Z: new NumberEntity(z, zPrecision?.length),
})),
VectorEntity.grammar.map(v => new this({
X: v.X,
@@ -33,27 +26,17 @@ export default class SimpleSerializationVectorEntity extends VectorEntity {
Z: v.Z,
}))
)
}
)
/**
* @template {typeof SimpleSerializationVectorEntity} T
* @this {T}
*/
static flagAllowShortSerialization(value = true) {
const result = this.asUniqueClass()
if (value !== result.allowShortSerialization) {
result.allowShortSerialization = value
result.grammar = result.createGrammar()
}
return result
}
doSerialize() {
const attributeSeparator = /** @type {typeof SimpleSerializationVectorEntity} */(
this.constructor
).attributeSeparator
return this.X.serialize() + attributeSeparator
+ this.Y.serialize() + attributeSeparator
+ this.Z.serialize() + (this.trailing ? attributeSeparator : "")
toString(
insideString = false,
indentation = "",
Self = this.Self(),
printKey = Self.printKey,
wrap = Self.wrap,
) {
return this.X.toString() + Self.attributeSeparator
+ this.Y.toString() + Self.attributeSeparator
+ this.Z.toString() + (this.trailing ? Self.attributeSeparator : "")
}
}

View File

@@ -1,57 +1,36 @@
import P from "parsernostrum"
import Utility from "../Utility.js"
import IEntity from "./IEntity.js"
export default class StringEntity extends IEntity {
static grammar = this.createGrammar()
static escapedCharacters = /['"\\]/g
static unescapedBackslash = /(?<=(?:[^\\]|^)(?:\\\\)*)\\(?!\\)/
static grammar = /** @type {P<StringEntity>} */(
P.doubleQuotedString
.map(insideString => new this(Utility.unescapeString(insideString)))
.label("StringEntity")
)
/** @param {String} value */
constructor(value = "") {
super()
this.value = value
}
/** @returns {P<StringEntity>} */
static createGrammar() {
return P.doubleQuotedString
.map(insideString => new this(StringEntity.unescape(insideString)))
.label("StringEntity")
}
/** @param {String} value */
static escape(value, inline = true) {
let result = value.replaceAll(new RegExp(`(${StringEntity.escapedCharacters.source})`, "g"), '\\$1')
if (inline) {
result = result
.replaceAll("\n", "\\n") // Replace newline with \n
.replaceAll("\t", "\\t") // Replace tab with \t
}
return result
}
/** @param {String} value */
static unescape(value) {
return value
.replaceAll(new RegExp(StringEntity.unescapedBackslash.source + "t", "g"), "\t") // Replace tab with \t
.replaceAll(new RegExp(StringEntity.unescapedBackslash.source + "n", "g"), "\n") // Replace newline with \n
.replaceAll(new RegExp(`\\\\(${StringEntity.escapedCharacters.source})`, "g"), "$1")
}
doSerialize(insideString = false) {
let result = `"${StringEntity.escape(this.value)}"`
if (insideString) {
result = StringEntity.escape(result, false)
}
return result
}
valueOf() {
return this.value
}
toString() {
return this.value
toString(
insideString = false,
indentation = "",
Self = this.Self(),
printKey = Self.printKey,
wrap = Self.wrap,
) {
let result = `"${Utility.escapeString(this.value)}"`
if (insideString) {
result = Utility.escapeString(result, false)
}
return result
}
}

21
js/entity/SymbolEntity.js Executable file → Normal file
View File

@@ -8,28 +8,17 @@ export default class SymbolEntity extends IEntity {
fromAttribute: (value, type) => new this(value),
toAttribute: (value, type) => value.toString()
}
static grammar = this.createGrammar()
/** @returns {P<SymbolEntity>} */
static createGrammar() {
return Grammar.symbol.map(v => new this(v)).label("SymbolEntity")
}
static grammar = /** @type {P<SymbolEntity>} */(
Grammar.symbol.map(v => new this(v)).label("SymbolEntity")
)
constructor(value = "") {
super()
this.value = value
}
serialize(
insideString = false,
indentation = "",
Self = /** @type {typeof IEntity} */(this.constructor),
) {
let result = this.value
if (Self.serialized) {
result = `"${result}"`
}
return result
valueOf() {
return this.value
}
toString() {

9
js/entity/TerminalTypeEntity.js Executable file → Normal file
View File

@@ -14,7 +14,9 @@ export default class TerminalTypeEntity extends IEntity {
bTerminalIsWeakPointer: BooleanEntity,
bTerminalIsUObjectWrapper: BooleanEntity,
}
static grammar = this.createGrammar()
static grammar = /** @type {P<TerminalTypeEntity>} */(
Grammar.createEntityGrammar(this)
)
constructor(values) {
super(values)
@@ -24,9 +26,4 @@ export default class TerminalTypeEntity extends IEntity {
/** @type {Boolean} */ this.bTerminalIsWeakPointer
/** @type {Boolean} */ this.bTerminalIsUObjectWrapper
}
/** @returns {P<TerminalTypeEntity>} */
static createGrammar() {
return Grammar.createEntityGrammar(this)
}
}

20
js/entity/UnknownKeysEntity.js Executable file → Normal file
View File

@@ -1,23 +1,11 @@
import P from "parsernostrum"
import Grammar from "../serialization/Grammar.js"
import GuidEntity from "./GuidEntity.js"
import IEntity from "./IEntity.js"
export default class UnknownKeysEntity extends IEntity {
static attributes = {
...super.attributes,
VariableGuid: GuidEntity,
}
static grammar = this.createGrammar()
static {
IEntity.unknownEntity = this
}
/** @returns {P<UnknownKeysEntity>} */
static createGrammar() {
return P.seq(
static grammar = /** @type {P<UnknownKeysEntity>} */(
P.seq(
// Lookbehind
P.reg(new RegExp(`(${Grammar.Regex.Path.source}|${Grammar.Regex.Symbol.source}\\s*)?\\(\\s*`), 1),
P.seq(Grammar.attributeName, Grammar.equalSeparation).map(([attribute, equal]) => attribute)
@@ -37,5 +25,9 @@ export default class UnknownKeysEntity extends IEntity {
attributes.forEach(attributeSetter => attributeSetter(values))
return new this(values)
}).label("UnknownKeysEntity")
)
static {
IEntity.unknownEntity = this
}
}

View File

@@ -1,22 +1,12 @@
import P from "parsernostrum"
import Grammar from "../serialization/Grammar.js"
import GuidEntity from "./GuidEntity.js"
import PinEntity from "./PinEntity.js"
export default class UnknownPinEntity extends PinEntity {
static attributes = {
...super.attributes,
PinId: GuidEntity
}
static grammar = this.createGrammar()
/** @returns {P<UnknownPinEntity>} */
static createGrammar() {
return P.seq(
// Lookbehind
P.reg(new RegExp(`(${Grammar.Regex.Symbol.source}\\s*)?\\(\\s*`), 1),
static grammar = /** @type {P<UnknownPinEntity>} */(
P.seq(
P.reg(new RegExp(`(${Grammar.Regex.Symbol.source})\\s*\\(\\s*`), 1),
Grammar.createAttributeGrammar(this).sepBy(Grammar.commaSeparation),
P.reg(/\s*(?:,\s*)?\)/)
).map(([lookbehind, attributes, _2]) => {
@@ -28,5 +18,5 @@ export default class UnknownPinEntity extends PinEntity {
attributes.forEach(attributeSetter => attributeSetter(values))
return new this(values)
}).label("UnknownPinEntity")
}
)
}

View File

@@ -14,7 +14,9 @@ export default class VariableReferenceEntity extends IEntity {
MemberGuid: GuidEntity,
bSelfContext: BooleanEntity,
}
static grammar = this.createGrammar()
static grammar = /** @type {P<VariableReferenceEntity>} */(
Grammar.createEntityGrammar(this).label("VariableReferenceEntity")
)
constructor(values) {
super(values)
@@ -23,9 +25,4 @@ export default class VariableReferenceEntity extends IEntity {
/** @type {InstanceType<typeof VariableReferenceEntity.attributes.MemberGuid>} */ this.MemberGuid
/** @type {InstanceType<typeof VariableReferenceEntity.attributes.bSelfContext>} */ this.bSelfContext
}
/** @returns {P<VariableReferenceEntity>} */
static createGrammar() {
return Grammar.createEntityGrammar(this).label("VariableReferenceEntity")
}
}

Some files were not shown because too many files have changed in this diff Show More