remove registry, use tree lookups (#828)

* remove images from registry, use tree lookups

* dont need registry for images anymore

* remove registry

* remove registry, apply groupdefs using lookups

* change function name

* simplify direct descendants

* remove prev siblings methods, just check inside loop for node

* comment on search

* handle groupdefs we dont find

* handle not finding specific id in first <elements> we find

* abstract out findInLexicalScope

* Add Js prefix for non-Maki objects
This commit is contained in:
jberg 2019-08-04 14:14:22 -07:00 committed by Jordan Eldredge
parent 8ee3882aba
commit 8a2aef62f4
8 changed files with 205 additions and 82 deletions

View file

@ -7,8 +7,6 @@ const System = require("./runtime/System");
const runtime = require("./runtime");
const interpret = require("./maki-interpreter/interpreter");
const SkinContext = React.createContext(null);
async function getSkin() {
// const resp = await fetch(process.env.PUBLIC_URL + "/skins/CornerAmp_Redux.wal");
const resp = await fetch(process.env.PUBLIC_URL + "/skins/simple.wal");
@ -43,6 +41,7 @@ function Container(props) {
}
function Layout({
node,
id,
background,
desktopalpha,
@ -54,13 +53,17 @@ function Layout({
droptarget,
children,
}) {
const data = React.useContext(SkinContext);
if (background == null) {
console.warn("Got a Layout without a background. Rendering null", id);
return null;
}
const image = data[background];
const image = node.js_imageLookup(background);
if (image == null) {
console.warn("Unable to find image to render. Rendering null", background);
return null;
}
return (
<>
<img
@ -80,13 +83,12 @@ function Layout({
);
}
function Layer({ id, image, children, x, y }) {
const data = React.useContext(SkinContext);
function Layer({ node, id, image, children, x, y }) {
if (image == null) {
console.warn("Got an Layer without an image. Rendering null", id);
return null;
}
const img = data[image.toLowerCase()];
const img = node.js_imageLookup(image.toLowerCase());
if (img == null) {
console.warn("Unable to find image to render. Rendering null", image);
return null;
@ -124,7 +126,6 @@ function Layer({ id, image, children, x, y }) {
}
function Button({ id, image, action, x, y, downImage, tooltip, node, children }) {
const data = React.useContext(SkinContext);
const [down, setDown] = React.useState(false);
const imgId = down && downImage ? downImage : image;
if (imgId == null) {
@ -132,7 +133,7 @@ function Button({ id, image, action, x, y, downImage, tooltip, node, children })
return null;
}
// TODO: These seem to be switching too fast
const img = data[imgId.toLowerCase()];
const img = node.js_imageLookup(imgId);
if (img == null) {
console.warn("Unable to find image to render. Rendering null", image);
return null;
@ -228,7 +229,7 @@ function XmlNode({ node }) {
function App() {
const [data, setData] = React.useState(null);
React.useEffect(() => {
getSkin().then(async ({ root, registry }) => {
getSkin().then(async root => {
// Execute scripts
await Utils.asyncTreeFlatMap(root, async node => {
switch (node.xmlNode.name) {
@ -238,12 +239,17 @@ function App() {
}
case "script": {
// TODO: stop ignoring standardframe
if (node.xmlNode.file.endsWith("standardframe.maki")) {
if (node.xmlNode.attributes.file.endsWith("standardframe.maki")) {
break;
}
const scriptGroup = Utils.findParentNodeOfType(node, ["group", "WinampAbstractionLayer", "WasabiXML"]);
const system = new System(scriptGroup);
await interpret({ runtime, data: node.xmlNode.script, system, log: false });
await interpret({
runtime,
data: node.js_annotations.script,
system,
log: false,
});
return node;
}
default: {
@ -252,19 +258,15 @@ function App() {
}
});
setData({ root, registry });
setData(root);
});
}, []);
if (data == null) {
return <h1>Loading...</h1>;
}
const { root, registry } = data;
const root = data;
return (
<SkinContext.Provider value={registry.images}>
<XmlNode node={root} />
</SkinContext.Provider>
);
return <XmlNode node={root} />;
}
export default App;

View file

@ -4,16 +4,15 @@ const WinampAbstractionLayer = require("./runtime/WinampAbstractionLayer");
const Layout = require('./runtime/Layout');
const Layer = require('./runtime/Layer');
const Container = require('./runtime/Container');
const JsElements = require("./runtime/JsElements");
const JsGammaSet = require("./runtime/JsGammaSet");
const JsGroupDef = require("./runtime/JsGroupDef");
const Group = require("./runtime/Group");
const Button = require("./runtime/Button");
const ToggleButton = require("./runtime/ToggleButton");
const Text = require("./runtime/Text");
const Status = require("./runtime/Status");
function splitValues(str) {
return str.split(",").map(parseFloat);
}
async function loadImage(imgUrl) {
return await new Promise(resolve => {
const img = new Image();
@ -123,12 +122,7 @@ const schema = {
const noop = (node, parent) => new MakiObject(node, parent);
const parsers = {
groupdef: (node, parent, registry) => {
const attributeId = node.attributes.id;
registry.groupdefs[attributeId] = node;
return new MakiObject(node, parent);
},
groupdef: (node, parent) => new JsGroupDef(node, parent),
skininfo: noop,
version: noop,
name: noop,
@ -139,39 +133,17 @@ const parsers = {
screenshot: noop,
container: (node, parent) => new Container(node, parent),
scripts: noop,
gammaset: (node, parent, registry) => {
const gammaId = node.attributes.id;
if (!registry.gammasets.hasOwnProperty(gammaId)) {
registry.gammasets[gammaId] = {};
}
return new MakiObject(node, parent);
},
gammaset: (node, parent) => new JsGammaSet(node, parent),
color: noop,
layer: (node, parent) => new Layer(node, parent),
layoutstatus: noop,
hideobject: noop,
button: (node, parent) => new Button(node, parent),
group: (node, parent, registry) => {
if (!node.children || node.children.length === 0) {
const groupdef = registry.groupdefs[node.attributes.id];
if (groupdef) {
const newNode = {
...node,
...groupdef,
attributes: { ...node.attributes, ...groupdef.attributes },
name: "group",
};
return new Group(newNode, parent);
}
}
return new Group(node, parent);
},
group: (node, parent) => new Group(node, parent),
layout: (node, parent) => new Layout(node, parent),
sendparams: noop,
elements: noop,
bitmap: async (node, parent, registry, zip) => {
elements: (node, parent) => new JsElements(node, parent),
bitmap: async (node, parent, zip) => {
let { file, gammagroup, h, id, w, x, y } = node.attributes;
// TODO: Escape file for regex
const img = Utils.getCaseInsensitveFile(zip, file);
@ -187,20 +159,21 @@ const parsers = {
x = x !== undefined ? x : 0;
y = y !== undefined ? y : 0;
}
registry.images[id.toLowerCase()] = { file, gammagroup, h, w, x, y, imgUrl };
return new MakiObject(node, parent);
return new MakiObject(node, parent, {
id,
file,
gammagroup,
h,
w,
x,
y,
imgUrl,
});
},
eqvis: noop,
slider: noop,
gammagroup: (node, parent, registry) => {
const gammaId = parent.xmlNode.attributes.id;
const attributeId = node.attributes.id;
const attributeValues = splitValues(node.attributes.value);
registry.gammasets[gammaId][attributeId] = attributeValues;
return new MakiObject(node, parent);
},
gammagroup: noop,
truetypefont: noop,
component: noop,
text: (node, parent) => new Text(node, parent),
@ -226,17 +199,13 @@ const parsers = {
menu: noop,
albumart: noop,
playlistplus: noop,
async script(node, parent, registry, zip) {
const { id, file, param } = node.attributes;
const script = await Utils.readUint8array(zip, file);
registry.scripts.push({ parent, id, param, script });
const newNode = { ...node, script, param, file };
return new MakiObject(newNode, parent);
async script(node, parent, zip) {
const script = await Utils.readUint8array(zip, node.attributes.file);
return new MakiObject(node, parent, { script });
},
};
async function parseChildren(node, registry, zip) {
async function parseChildren(node, zip) {
if (node.xmlNode.type === "comment") {
return;
}
@ -274,9 +243,12 @@ async function parseChildren(node, registry, zip) {
throw new Error(`Missing parser for ${childName}`);
return;
}
const parsedChild = await childParser(child, node, registry, zip);
if (parsedChild.xmlNode.children != null && parsedChild.xmlNode.children.length > 0) {
await parseChildren(parsedChild, registry, zip);
const parsedChild = await childParser(child, node, zip);
if (
parsedChild.xmlNode.children != null &&
parsedChild.xmlNode.children.length > 0
) {
await parseChildren(parsedChild, zip);
}
return parsedChild;
})
@ -287,11 +259,43 @@ async function parseChildren(node, registry, zip) {
node.js_addChildren(filteredChildren);
}
async function applyGroupDefs(root) {
await Utils.asyncTreeFlatMap(root, async node => {
switch (node.xmlNode.name) {
case "group": {
if (!node.children || node.children.length === 0) {
const groupdef = node.js_groupdefLookup(node.xmlNode.attributes.id);
if (!groupdef) {
console.warn(
"Unable to find groupdef. Rendering null",
node.xmlNode.attributes.id
);
return {};
}
node.children = groupdef.children;
// Do we need to copy the items instead of just changing the parent?
node.children.forEach(item => {
item.parent = node;
});
node.xmlNode.attributes = {
...node.xmlNode.attributes,
...groupdef.xmlNode.attributes,
};
}
return {};
}
default: {
return node;
}
}
});
}
async function initialize(zip, skinXml) {
const registry = { scripts: [], gammasets: {}, images: {}, groupdefs: {} };
const root = new WinampAbstractionLayer(skinXml.children[0], null);
await parseChildren(root, registry, zip);
return { root, registry };
await parseChildren(root, zip);
await applyGroupDefs(root);
return root;
}
export default initialize;

View file

@ -24,6 +24,7 @@ class GuiObject extends MakiObject {
return this.findobject(id);
}
init(newRoot) {
this.parent = newRoot;
newRoot.js_addChild(this);
return this;
}

View file

@ -0,0 +1,15 @@
const MakiObject = require("./MakiObject");
class JsElements extends MakiObject {
/**
* getclassname()
*
* Returns the class name for the object.
* @ret The class name.
*/
static getclassname() {
return "Elements";
}
}
module.exports = JsElements;

View file

@ -0,0 +1,15 @@
const MakiObject = require("./MakiObject");
class JsGammaSet extends MakiObject {
/**
* getclassname()
*
* Returns the class name for the object.
* @ret The class name.
*/
static getclassname() {
return "GammaSet";
}
}
module.exports = JsGammaSet;

View file

@ -0,0 +1,15 @@
const MakiObject = require("./MakiObject");
class JsGroupDef extends MakiObject {
/**
* getclassname()
*
* Returns the class name for the object.
* @ret The class name.
*/
static getclassname() {
return "GroupDef";
}
}
module.exports = JsGroupDef;

View file

@ -1,9 +1,11 @@
const Emitter = require("../Emitter");
const { findElementById, findGroupDefById } = require("../utils");
class MakiObject {
constructor(node, parent) {
constructor(node, parent, annotations = {}) {
this.xmlNode = node;
this.parent = parent;
this.js_annotations = annotations;
this.children = [];
this._emitter = new Emitter();
@ -42,6 +44,24 @@ class MakiObject {
this._emitter.dispose();
}
js_imageLookup(id) {
const element = findElementById(this, id);
if (element) {
return element.js_annotations;
}
return null;
}
js_groupdefLookup(id) {
const groupdef = findGroupDefById(this, id);
if (groupdef) {
return groupdef;
}
return null;
}
/**
* getclassname()
*

View file

@ -1,4 +1,6 @@
import { xml2js } from "xml-js";
const JsElements = require("./runtime/JsElements");
const JsGroupDef = require("./runtime/JsGroupDef");
export function getCaseInsensitveFile(zip, filename) {
// TODO: Escape `file` for rejex characters
@ -104,7 +106,7 @@ export function unimplementedWarning(name) {
// Operations on trees
export function findParentNodeOfType(node, type) {
let n = node;
while(n.parent !== null) {
while (n.parent) {
n = n.parent;
if ((!Array.isArray(type) && n.xmlNode.name === type) ||
(Array.isArray(type) && type.includes(n.xmlNode.name))) {
@ -129,7 +131,7 @@ export function findDescendantByTypeAndId(node, type, id) {
}
}
for(let i = 0; i < node.children.length; i++) {
for (let i = 0; i < node.children.length; i++) {
const child = node.children[i];
const descendant = findDescendantByTypeAndId(child, type, id);
if (descendant) {
@ -139,3 +141,52 @@ export function findDescendantByTypeAndId(node, type, id) {
return null;
}
function findDirectDescendantById(node, id) {
return node.children.find(item => item.xmlNode.attributes.id === id);
}
// Search up the tree for a node that is in `node`'s lexical scope and pred returns node
function findInLexicalScope(node, pred) {
let currentNode = node;
while (currentNode.parent) {
let parent = currentNode.parent;
const children = parent.children;
for (let i = 0; i < children.length; i++) {
const child = children[i];
if (child === currentNode) {
break;
}
const item = pred(child);
if (item) {
return item;
}
}
currentNode = parent;
}
return null;
}
// Search up the tree for <Elements> nodes that are in node's lexical scope.
// return the first child of an <Elements> that matches id
export function findElementById(node, id) {
return findInLexicalScope(node, child => {
if (child instanceof JsElements) {
const element = findDirectDescendantById(child, id);
if (element) {
return element;
}
}
});
}
// Search up the tree for a <GroupDef> node that is in node's lexical scope and matches id.
export function findGroupDefById(node, id) {
return findInLexicalScope(node, child => {
if (child instanceof JsGroupDef && child.xmlNode.attributes.id === id) {
return child;
}
});
}