excessive / DOMy

A DOM-like GUI framework for the *awesome* LÖVE framework
Other
32 stars 2 forks source link

Some thoughts on the Script API #8

Closed karai17 closed 9 years ago

karai17 commented 9 years ago

The Script API should vaguely resemble jQuery (but be less convoluted). Each object should have:

And probably other stuff, too.

The lists of siblings and children should be indexed arrays (you can loop through them via ipairs).

The parent, siblings, and children should be pointers to other objects.

There should be helper functions in place despite Lua's ability to access data directly.

local child = object.children[3] -- object
local other_child = object:get_child(3) -- object

Customized properties (properties not set by the object default, id, or class associated to an object) should be held in a custom properties table so when an object needs to be rerendered (say, if it is resized), the programatically defined properties will persist.

If anyone else has ideas, feel free to throw them in here and we can discuss them.

karai17 commented 9 years ago

Styles should also be able to be redefined programatically. If a class definition needs to be adjusted, then an API should be in place to allow that.

-- all values defined by dp units
local class = dom:get_class("some_class")
class.padding_left = 5
dom:render_ui()

Is there a smart and efficient way to "monitor" when a table is adjusted, or should the user be required to manually render the ui?

pablomayobre commented 9 years ago

I guess that the drawing will be done somewhere in love.draw so dom:render_ui() should not be necessary... Just change the appropriate value, and when rendering the UI system will look at the values, and render the element accordingly.

That is, what I, as a user, expect

karai17 commented 9 years ago

Well my issue is that I don't want to need to update every single object on every single frame, setting multiple properties multiple times depending on how many classes an object has. Perhaps I am over thinking it, but that seems like it could get slow fairly quickly when you could just render the data and update it as needed.

pablomayobre commented 9 years ago

Why would it be slow? All you need to know is where it is positioned, how it is styled... for each instance... It is what most UI toolkit do, you just need a table for each object and a table for changes. When you update you look if there where changes, look at what needs to be changed and the proceed with rendering... But it's not like someone will change a property every frame that affects 1000 elements. Most users of this library will create relatively simple UIs with no more than 100 elements in total

shakesoda commented 9 years ago

it's pretty easy to burn through hundreds of elements in a single window with, say, an inventory menu. it should be designed to be as efficient as possible, I've had enough of dog slow UIs in games because people didn't think ahead when designing them.

pablomayobre commented 9 years ago

Yeah... A good way to parse through it is necessary for sure, like having tables that group by id, element, class and so. Also an efficient algorithm to get which element matches, for example when you ask for the buttons childs of an element with id "Hello" and the classes "Pium" and "Bang"... I guess that knowing all that is REALLY HARD... How does SQL does it?

karai17 commented 9 years ago

In the above image, I spot about 60 UI elements in a single tab on a single window, including the tooltip. If we assume we only need to check for updates on visible units, I can expect several hundred visible UI elements at any given time in a well polished game.

So if we use 500 elements as an average game, 1000 elements as a high end game, and 5000 elements as an extreme example of "I want to create my own spreadsheet program", what are we looking at in benchmarks if we check for updates every frame?

pablomayobre commented 9 years ago

Well yeah then my games are CRAP... I try to use no more than 10 UI elements hahaha

The matching algorithm will be the most important thing...

karai17 commented 9 years ago

When looking for all the children of an element with the classes "x" and "y", you'd probably just do several table lookups/iterations.

local children = dom:get_element_by_id("some_element").children
local filter = dom:filter_elements_by_class(children, "x", "y")
karai17 commented 9 years ago

You might be misunderstanding what, specifically, a UI element is. If you look at that picture above, we have a window frame. That window frame has a title (textbox), a search box (which contains an image and a textbox), and a tab list. that first tab contains an image and a textbox and two buttons, then it has a list object. that list object has several lists items and each item has several images, and so on and so forth.

What might look like only 11 or 12 elements quickly becomes 60+ when looked at in finer detail.

adrix89 commented 9 years ago

Why check for updates? I don't understand. When things change you know when it happens and update the structure accordingly. You should be able to get the table reference to the element that is changed and update the draw graphic.

karai17 commented 9 years ago

We know when things change, but the renderer does not.

adrix89 commented 9 years ago

Why does the rendered not automatically use updated data when it draws? Are you messing with sprite-batches or canvas?

karai17 commented 9 years ago

That's the question at hand. Do we re-render the elements every frame with constantly updated data, or do we render once and re-render on changes? Depending on just how fast rendering is, we may be able to update 5000 elements every frame.

adrix89 commented 9 years ago

Depends on how it handles opacity. Keep the previous data and draw the differences. If you can't do that its probably better to do it every frame. When make changes it will put it on an the draw difference part of the draw function.

karai17 commented 9 years ago

It might be worth noting that when I say render, I mean go through each object and apply styles from various places. This involves many table look ups (a minimum of 3 per individual element).

adrix89 commented 9 years ago

When you do that regenerate a new spritebatch since the structure fundamentally changed. Draw differences are only useful for small changes like hovers and clicks. Also animated or fast changing parts should not be part of the spritebatch. Have multiple layers of sprite batches and sandwich the animated parts.

karai17 commented 9 years ago

Yeah, I was also starting to think about how to manage draw calls.

adrix89 commented 9 years ago

Separate constantly updated data and animations, treat them as separate from static elements. That way you can update them as much as you want without messing with the structure of static elements.

Best of both worlds.

karai17 commented 9 years ago

But what if some "static" elements are affected by the animations of other elements? For example, clicking on an element might execute an animation that grows the size of said element, pushing other elements down. This would require those elements and all of their recursive children to have updated absolute positions (for drawing purposes).

In that sense, I don't think it is possible to distinguish between "static" and "animated".

adrix89 commented 9 years ago

That is a fundamental change of structure rather then just static and animated. Fundamental changes should be treated differently until they are at 'rest' in which case you should be able to use the normal static and animated.

You need to have two draw functions, one that is used for static elements and can use spritebatches. And the other drawing them every frame with the normal love draw function. When complicated stuff happens it should default to the draw one.

ALL of the structure should be able to be drawn with the normal draw function. We only chose the other one when things are static.

pablomayobre commented 9 years ago

I dont think the static and animated idea is quite right... everything is animated... Just not always...

An answer would be to turn:

element.css.position = "absolute"

into:

element.css.position"absolute"

or

element.css.width(50)

That way you know something changed

pablomayobre commented 9 years ago

What if we thread this? The thread takes all the information of the changes, updates the elements and returns a function that renders everything, so the updating of elements wont slow down the main thread

karai17 commented 9 years ago

Threading would definitely help with speed. I am starting to lean more towards updating every object every frame in a separate thread, but can we guarantee that the entire UI will be updated by the time it is to be drawn?

pablomayobre commented 9 years ago

YES! The difference will be a frame or two... Nothing the eye can detect. I have already tried this and it worked amazingly

adrix89 commented 9 years ago

GUI and threading is a mess. Remember that everything that has a state is bad news for threading. Updating elements also isn't that costly if you do it smart.

karai17 commented 9 years ago

Well, in my mind the most sensible way to update every single element would be to:

1) Grab every root element (an element with no parent) 2) recursively go through the children to update positions, etc.

We need to do it that way because we need to define the absolute position for drawing purposes.