Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Multiple paint layers #57

Merged
merged 27 commits into from
Oct 5, 2024
Merged

Multiple paint layers #57

merged 27 commits into from
Oct 5, 2024

Conversation

timothyschoen
Copy link
Contributor

Draft for multi-layer painting for pdlua.

Currently, it works like this:

Apart from your normal paint function, you can define extra draw callbacks that draw on top of it:

function hello:paint(g)
    g:set_color(50, 50, 50)
    g:fill_all()
end

function hello:paint_layer_2(g)
    g:set_color(250, 84, 108, 1)
    g:fill_ellipse(self.circle_x, self.circle_y, self.circle_radius, self.circle_radius)
    pd.post("layer 2")
end

function hello:paint_layer_3(g)
    -- Draggable rectangle
    g:set_color(66, 207, 201, 1)
    g:fill_rounded_rect(self.draggable_rect_x, self.draggable_rect_y, self.draggable_rect_size, self.draggable_rect_size, 5)
    g:set_color(0, 0, 0, 1)
    g:draw_text("Drag\n me!", self.draggable_rect_x + 8, self.draggable_rect_y + 10, self.draggable_rect_size, 12)
end

Then, you can repaint the layers separately by calling

repaint()   -- Repaints all layers
repaint(0) -- Also repaints all layers
repaint(1)  -- Repaints only your regular paint(g) function
repaint(2)  -- Repaints only the paint_layer_2 function
repaint(N)  -- Repaints only the paint_layer_N function

The primary motivation is performance. If you have a static background with moving foreground, it won't have to repaint everything all the time.

Currently implemented for pd-vanilla and plugdata, so purr-data still needs to be done. I might even have broken the purr-data implementation a bit. So far it seems to work well, but the pd-vanilla version could use some more testing.

@timothyschoen
Copy link
Contributor Author

timothyschoen commented Sep 27, 2024

hello-gui.txt

Here's the hello-gui example with multi-layer drawing. Unfortunately, github doesn't accept .pd_lua files

@timothyschoen
Copy link
Contributor Author

Screenshot 2024-09-27 at 03 56 31

Btw, after merging in the latest changes, I get this error in plugdata. Any idea why?

Seems like the code is only passing one string to _setrequirepath while the lua code expects 2 arguments, or maybe I messed something up while merging.

@ben-wes
Copy link
Contributor

ben-wes commented Sep 27, 2024

cool! tested this with vanilla here - works perfectly well. can't respond to the _setrequirepath issue in plugdata though.

@agraef
Copy link
Owner

agraef commented Sep 27, 2024

Seems like the code is only passing one string to _setrequirepath while the lua code expects 2 arguments, or maybe I messed something up while merging.

I remember that I added the extra pdlua_path argument at some point in 0.12, but that's long gone again. So it looks like your pd-lua source is in an inconsistent state, and you're running an outdated pd.lua there. There have been lots of changes in the entire source since you last merged, so make sure that you get all of it. :)

@agraef
Copy link
Owner

agraef commented Sep 27, 2024

Two things I noticed:

  • The latest source already has a declaration for sys_trytoopenone in pdlua.c, can you please remove the one in your changeset? Or maybe change the existing one so that it works for you?

  • You renamed the class member in t_pdlua to pdlua_class, is there an important reason for that? As you might have noticed, I have a second "shadow" class for the gfx widget behavior now so that I can swap them out as needed. That member is still named class_gfx, so the naming is inconsistent now.

@agraef
Copy link
Owner

agraef commented Sep 27, 2024

Also, what about the repaint method, can it still be invoked without arguments as before? Otherwise it would break a lot of existing scripts, so I assume that the answer is yes. ;-)

@agraef
Copy link
Owner

agraef commented Sep 27, 2024

Other than that, the changeset looks good to me, I just need a little time for the purr-data port.

@agraef
Copy link
Owner

agraef commented Sep 27, 2024

Also, Ben, thanks for testing the vanilla variant, it's good to know that this works, since it means that probably all I'll have to do is to massage the JavaScript code!

@timothyschoen
Copy link
Contributor Author

The latest source already has a declaration for sys_trytoopenone in pdlua.c, can you please remove the one in your changeset? Or maybe change the existing one so that it works for you?

I'll do that! Probably put that in at some point to get it to compile, before merging your changes.

You renamed the class member in t_pdlua to pdlua_class, is there an important reason for that? As you might have noticed, I have a second "shadow" class for the gfx widget behavior now so that I can swap them out as needed. That member is still named class_gfx, so the naming is inconsistent now.

Yeah, the plugdata-side implementation for pdlua imports the "pdlua.h" file, and "class" is a reserved keyword in C++, so it wouldn't compile. By changing the to "pdlua_class", the header is valid C and C++ code. Now I'm writing this though, I realise that I should have done "extern 'C'" instead, so I guess we could revert it.

@ben-wes
Copy link
Contributor

ben-wes commented Sep 29, 2024

just quickly adding to this: Pd crashes with some of my graphical objects now. i'll investigate more later what the relevant difference there is between the gui example and my objects!

@agraef
Copy link
Owner

agraef commented Sep 29, 2024

Now I'm writing this though, I realise that I should have done "extern 'C'" instead, so I guess we could revert it.

If it works, please do. Otherwise the name of the shadow class needs to be changed accordingly. I despise inconsistent naming, it makes code harder to read.

Pd crashes with some of my graphical objects now.

That's not good, please report it here so that Tim can fix it. Pd-Lua gfx code should never crash Pd, no matter how broken it is.

@ben-wes
Copy link
Contributor

ben-wes commented Sep 29, 2024

@timothyschoen one finding concerning the crash: it's obviously connected to a clocked repaint() or repaint(0) method. your new example above works for me. but the old one also crashes here (mac m2).

@ben-wes
Copy link
Contributor

ben-wes commented Sep 29, 2024

Also, what about the repaint method, can it still be invoked without arguments as before?

yep - tim mentioned that above: repaint() -- Repaints all layers

@timothyschoen
Copy link
Contributor Author

Now I'm writing this though, I realise that I should have done "extern 'C'" instead, so I guess we could revert it.

If it works, please do. Otherwise the name of the shadow class needs to be changed accordingly. I despise inconsistent naming, it makes code harder to read.

Unfortunately, it didn't work. Bascially any name that isn't "class" would be okay for me.

Pd crashes with some of my graphical objects now.

That's not good, please report it here so that Tim can fix it. Pd-Lua gfx code should never crash Pd, no matter how broken it is.

@timothyschoen one finding concerning the crash: it's obviously connected to a clocked repaint() or repaint(0) method. your new example above works for me. but the old one also crashes here (mac m2).

Gonna check now why that is!

@timothyschoen
Copy link
Contributor Author

Seems like the code is only passing one string to _setrequirepath while the lua code expects 2 arguments, or maybe I messed something up while merging.

I remember that I added the extra pdlua_path argument at some point in 0.12, but that's long gone again. So it looks like your pd-lua source is in an inconsistent state, and you're running an outdated pd.lua there. There have been lots of changes in the entire source since you last merged, so make sure that you get all of it. :)

Ah, that's probably it. Thanks!

@timothyschoen
Copy link
Contributor Author

timothyschoen commented Sep 29, 2024

I think f8e94a3 fixes the crash. The indexing for layers was a bit messy, since I kept the 1-indexing from lua in the C code, which also made it allocate space for one more layer than we need. This messy logic led me to make a mistake that caused the crash. Should be all good now.

Btw: comparing the hello-gui example in pd-vanilla with and without layers makes a huge difference over here. With layers, it easily stays at 60fps, without layers the framerate drops pretty low

@ben-wes
Copy link
Contributor

ben-wes commented Sep 29, 2024

cool, thanks! for some reason, i now get errors when redrawing the first or all layers in a little test patch:

"uplevel #0 $docmds"(Tcl) UNHANDLED ERROR: tagOrId ".l115c9071c0" doesn't match any items
    while executing
".x600000f80600.c lower {.l015c9071c0} {.l115c9071c0} "
    ("uplevel" body line 6)
    invoked from within
"uplevel #0 $docmds"

... your new hello-gui.pd_lua looks good though and also the previous version works again. i currently don't see what i'm doing wrong. just using this test:

local t = pd.Class:new():register("gui-test")

function t:initialize(sel, atoms)
    self.inlets = 1
    self.delay_time = 50
    self:set_size(200, 200)
    return true
end

function t:postinitialize()
    self.clock = pd.Clock:new():register(self, "tick")
    self.clock:delay(self.delay_time)
end

function t:finalize()
    self.clock:destruct()
end

function t:paint(g)
    local b = math.random()*255
    g:set_color(b, b, b);
    g:fill_all();
end

function t:paint_layer_2(g)
    local b = math.random()*255
    g:set_color(b, b, b);
    g:fill_rect(1, 1, 99, 99);
end

function t:tick()
    self:repaint(1)
    self.clock:delay(self.delay_time)
end

EDIT: it's still drawing the 1st layer then - but with errors. if i do just self:repaint(), it's also drawing the first layer (with errors), but not the second one.

@ben-wes
Copy link
Contributor

ben-wes commented Sep 29, 2024

ah, i see now that a bang to [hello_gui] also gives me this error - so it's obviously connected to redrawing the first layer via repaint(), repaint(0) or repaint(1).

@timothyschoen in this context: i guess, the syntax for the layers is intuitive and analogous to the inlet messages - i'm just wondering if paint_layer_1(g) should also be allowed synonymous to just paint(g)?

@timothyschoen
Copy link
Contributor Author

ah, i see now that a bang to [hello_gui] also gives me this error - so it's obviously connected to redrawing the first layer via repaint(), repaint(0) or repaint(1).

@timothyschoen in this context: i guess, the syntax for the layers is intuitive and analogous to the inlet messages - i'm just wondering if paint_layer_1(g) should also be allowed synonymous to just paint(g)?

Ah, that makes sense, since I did change the logic for that. Must've made a mistake somewhere. I'll take look, should be fixable!

@agraef
Copy link
Owner

agraef commented Sep 29, 2024

Unfortunately, it didn't work. Bascially any name that isn't "class" would be okay for me.

Yeah, that's because it's a reserved keyword in C++. Then keep it the way it is, but please also change class_gfx to pdlua_class_gfx.

Looking at pd.Class:repaint in pd.lua, it looks like the indentation is messed up. Also, there's a lot of repeated or similar code in there now, maybe the entire method could be refactored so that it becomes a little tidier again?

OK, I'll wait until you fixed the teething problems before I have a go at porting all the gui changes.

Note to self:

  • Also need to check that pdx.lua still works.
  • Also need to update the documentation (doc/graphics.txt, pdlua-help.pd -- graphics subpatch and pdlua/hello-gui.pd_lua, see Tim's attachment above).
  • Check which of the other gui examples need fixing up to make them work better, without the frame rate throttling kludges that I added earlier if possible.
  • Update the tutorial with an example showing how to use layers, or at least add a callout about that feature.

(Ben, you could maybe give me a helping hand with any of these items if you have the time.)

@timothyschoen
Copy link
Contributor Author

Should be fixed!

@ben-wes
Copy link
Contributor

ben-wes commented Sep 30, 2024

Should be fixed!

can confirm that the warning is gone and it works now for a single gui object instance!

if i load a patch with 2 instances, one is somehow "stealing" the first layer though. this also happens when i'm redrawing the first layer of one: it's disappearing for the other object then.

cutting one of the objects then also gives me:

(Tcl) UNHANDLED ERROR: tagOrId "(null)" doesn't match any items
    while executing
".x600000a9c300.c raise {.l1150c06fb0} {(null)} "
    ("uplevel" body line 6)
    invoked from within
"uplevel #0 $docmds"

@agraef
Copy link
Owner

agraef commented Oct 4, 2024

I'd say that if pdlua doesn't start without this file, we should put it in the top level directory of the repo

You're right. Inertia and laziness prevented that so far. :) There's also some other files that should go there, have a look at the definition of pdx_files in the toplevel pd-lua Makefile. (At least pd-remote.pd will be needed for the live-coding support.)

@agraef
Copy link
Owner

agraef commented Oct 4, 2024

Ok, I pulled your branch into my working copy, and started a deep dive into the source now, in order to figure out what exactly I need to change in the PURR_DATA part. There's still some stuff that I need to understand a little better, it would be nice if you could help me out there. (Sorry if you already provided that information, but this thread is getting a bit long, and I don't have the time to dig through it again.)

  • As I understand it, layer 0 (and thus layer_tags[0]) is never used, right? But shouldn't layer_tags[0] at least be initialized to something, if only NULL? So that you get a defined value in case of an off-by-one bug?
  • In start_paint() at L838, you retrieve the layer number from argument 2. I think that this will give a Lua exception if that argument isn't present, which is good. But what happens if the argument is zero or negative? You'd then access layer_tags[0] or, worse, layer_tags[i] with i<0. Are you really absolutely 100% sure that this can never happen? If so, why? I'd say that a little bit of defensive programming can't hurt there, so it would be prudent to check that layer is > 0 and bail out if it's not, to prevent crashing Pd.
  • At L841-846 you're creating new layer tags as needed. But you're only initializing the current one. What happens if layer != gfx->num_layers? Again, are you really absolutely 100% sure that this can never happen? If so, why? Imagine that the user defines paint_layer_2 and paint_layer_4, but not paint_layer_3 at first? But then realizes his mistake and defines paint_layer_3 afterwards and reloads the script using live-coding. In that case layer_tags[3] will still be uninitialized which will crash Pd or at least cause some damage somewhere else because of the uninitialized char*.

I think that it's for now, but I'll let you know if I run into other problems.

@agraef
Copy link
Owner

agraef commented Oct 4, 2024

Note that by "really absolutely 100% sure" I mean "could produce a mathematical proof of the fact if I needed to". In all other cases defensive programming is in order! 😏 Better safe than sorry, IMHO.

@agraef agraef mentioned this pull request Oct 4, 2024
@agraef
Copy link
Owner

agraef commented Oct 4, 2024

Another question: Where is the memory of the layer tags (both the char * pointers and the array itself) freed again? I don't see anything in the code.

@timothyschoen
Copy link
Contributor Author

timothyschoen commented Oct 4, 2024

Ok, I pulled your branch into my working copy, and started a deep dive into the source now, in order to figure out what exactly I need to change in the PURR_DATA part. There's still some stuff that I need to understand a little better, it would be nice if you could help me out there. (Sorry if you already provided that information, but this thread is getting a bit long, and I don't have the time to dig through it again.)

No problem!

As I understand it, layer 0 (and thus layer_tags[0]) is never used, right? But shouldn't layer_tags[0] at least be initialized to something, if only NULL? So that you get a defined value in case of an off-by-one bug?

Layer tag 0 should get initialised and used. It's associated with the regular paint(g) call. It's at least used when we call pdlua_gfx_clear, to clear the first layer when it needs to be updated. It also gets used in end_paint to do ordering. Note that we get the layer index as luaL_checknumber(L, 2) - 1, so we change it to be 0-indexed.

In start_paint() at L838, you retrieve the layer number from argument 2. I think that this will give a Lua exception if that argument isn't present, which is good. But what happens if the argument is zero or negative? You'd then access layer_tags[0] or, worse, layer_tags[i] with i<0. Are you really absolutely 100% sure that this can never happen? If so, why? I'd say that a little bit of defensive programming can't hurt there, so it would be prudent to check that layer is > 0 and bail out if it's not, to prevent crashing Pd.

The call to pdlua_start_paint is made in pd.lua, so we can control the values that go in there. So it shouldn't be able to go wrong. If you want to add an extra check in case the user incorrectly modifies pd.lua, that's fine with me.

At L841-846 you're creating new layer tags as needed. But you're only initializing the current one. What happens if layer != gfx->num_layers? Again, are you really absolutely 100% sure that this can never happen? If so, why? Imagine that the user defines paint_layer_2 and paint_layer_4, but not paint_layer_3 at first? But then realizes his mistake and defines paint_layer_3 afterwards and reloads the script using live-coding. In that case layer_tags[3] will still be uninitialized which will crash Pd or at least cause some damage somewhere else because of the uninitialized char*.

I see what you mean, I thought I made sure that can't happen but looks like it can happen! So we need to fix that. Leaving holes is not allowed currently, but it also shouldn't crash because of it. I've just made a change to pd.lua that should make sure this can't happen (and also makes it easier to read), but if you want to be extra sure, we can also update the C code to make sure it won't crash. I think it should never happen, but the safer the better.

@timothyschoen
Copy link
Contributor Author

Another question: Where is the memory of the layer tags (both the char * pointers and the array itself) freed again? I don't see anything in the code.

Oops! I still need to do that

@timothyschoen
Copy link
Contributor Author

timothyschoen commented Oct 4, 2024

Looks to me like transforms can also potentially leak, since it only gets freed when we reset them in start_paint. I guess we need a pdlua_gfx_free function to clean up the layers and transforms.

@timothyschoen
Copy link
Contributor Author

timothyschoen commented Oct 4, 2024

I fixed the memory leaks, hope it's not too much of a hassle for you to merge in. Also confirmed with leak detection that we don't leak anything anymore.

@timothyschoen
Copy link
Contributor Author

timothyschoen commented Oct 4, 2024

Random thought, but perhaps we should heap allocate the pdlua_gfx object, so that it doesn't take up memory for non-gui objects? Not a priority right now I guess.

@timothyschoen
Copy link
Contributor Author

Sorry for the many commits btw, the draft was less complete than I had hoped it was!

@agraef
Copy link
Owner

agraef commented Oct 4, 2024

No worries, my changes are confined to the PURR_DATA parts (at least so far), so I can just rebase on your latest and be done with it.

(Also feel free to force-push if needed, I know how to deal with that, no problem.)

@agraef
Copy link
Owner

agraef commented Oct 4, 2024

I've just made a change to pd.lua that should make sure this can't happen

Ok, I see what you did there in 696d227, but it's not helping, sorry. :) Let me elaborate.

Your proof of correctness rests on the assumption that pd.Class:repaint(0) is called before anything else so that the entire layer tags array gets populated there, and that's initially true. But that assumption breaks down with live-coding, just as it did before, and your commit doesn't help with this.

Consider this use case: The user starts out with a simple animation with just one layer, layer 1. The object is created with this layer, at which point pd.Class:repaint(0) gets called and the tags table is populated with one entry. (Or maybe 2? I still don't quite get your numbering scheme there, I'm afraid.)

Then the user adds two extra layers, layer 2 and 3, and adds messages to kick off those parts of the animation using separate clocks. He reloads the script, which will not cause pd.Class:repaint(0) to be called again, because the object is already instantiated and running. You can not prevent this from happening, because the raison d'être of live-coding is precisely that the underlying Lua code can be swapped out at any time without any disruptive state changes in already running instances of the object.

While the animation keeps running, subsequent pd.Class:repaint() calls will pick up the new layers 2 and 3 and, as they are consecutive, will add them both to the paint_funcs table. But their entries in the layer tags array on the C side still haven't been created yet, because pd.Class:repaint(0) didn't run (and never will again in this example).

But now the user, let's call him Albert D'Evil, adds the requisite messages for the layer 2 and 3 animations to his patch and decides that he wants to kick off the layer 3 animation first, because ... why not? :)) So pd.Class:repaint(3) is called and there you got your hole at index 2 in the tag table now. As soon as the user then kicks off the layer 2 animation, Pd will crash and burn.

You see, with live coding the whole environment becomes a whole lot more dynamic, so all your assumptions about the execution sequence go out of the window, no matter how hard you try. The only way to fix this problem is to cope with the out-of-order layer initializations on the C side when populating the tags table. And it's very easy to do, just do the snprintf thing right there in in start_paint() with all newly created layer tags instead of just a single one.

I also don't like that you're now constructing and checking the entire paint_funcs table each time pd.Class:repaint() is called, even if it's just to update a single layer. That might become a performance hog if you have many layers, because in an animation that method might be called many times per second, and it will then have to reconstruct the entire layer functions table each time. So it kind of defeats the purpose of having layers which can be updated independently in the first place.

TL;DR: As 696d227 doesn't really help with the out-of-order layer tag creation problem, and it introduces substantial overhead for no real gain, I'd revert it, and implement the proper incremental initialization of the tags table in start_paint() that I sketched out above instead. Problem solved. :)

Of course, I can do it for you if I didn't explain it clearly enough.

@agraef
Copy link
Owner

agraef commented Oct 4, 2024

One more thing: You're using resizebytes() to resize the layer tags array in start_paint(), but malloc() for the initial allocation, and free() for deallocation in pdlua_gfx_free(). Which might be inconsequential if Pd's underlying implementation in fact uses malloc/realloc/free. But in any case it's inconsistent -- please use either malloc/realloc/free or their Pd equivalents, I think that's safer.

@timothyschoen
Copy link
Contributor Author

timothyschoen commented Oct 4, 2024

which will not cause pd.Class:repaint(0) to be called again

Ah, I hadn't considered that. Makes sense, gonna work on it! I'll also revert the pd.lua change.

And it's very easy to do, just do the snprintf thing right there in in start_paint() with all newly created layer tags instead of just a single one

I'm not so sure, because when this happens in end_paint:

 pdgui_vmess(0, "crss", cnv, "raise", gfx->current_layer_tag, gfx->layer_tags[layer - 1]);

It will try to position layer 3 on top of layer 2, but tcl/tk is not aware layer 2 exists yet, so it will print an error. So we need to find a way around that too.

@timothyschoen
Copy link
Contributor Author

The layer indices in Lua should be:

0: Not a real layer, but will trigger a repaint of all layers
1: Regular paint(g) function call
2: paint_layer_2(g) function call
n: paint_layer_n(g) function call

When we convert that into C, we subtract 1 so that we can use it as an index for the layers array, so:
-1: not a real layer, full repaint
0: repaint(g)
etc.

Hope that helps, let me know if you spot anywhere I did it wrong.

@timothyschoen
Copy link
Contributor Author

timothyschoen commented Oct 4, 2024

That last problem is kind of a fundamental one. We can't render on top of layer 2 if it doesn't exist. I see 2 possible solutions:

Solution 1: We create an invisible dummy tag for skipped layers, so we can order things correctly. We'll need to correctly position the dummy layer, and after that the real layer relative to that. IMO: this feels bad because now we are in a state where layer 3 is visible but layer 2 is not. I don't feel like that should be possible. I also feel like this would be a lot of code just to make something work that's not actually valid.

Solution 2: If we notice a hole in the paint function chain, we trigger a full repaint. We consider it an invalid state, and solve it by re-rendering everything. I've verified that this works perfectly and solves all our problems, but it feels pretty nasty code-wise, because we're synchronously repainting all the layers, inside a layer repaint call:

        int layer = luaL_checknumber(L, 2) - 1;
        if(layer > gfx->num_layers) // If we get here, we have skipped a layer. This isn't allowed, so we should instead repaint everything
        {
            pdlua_gfx_repaint(obj, 0);
            lua_pushnil(L);
            return 1;
        }
        else if(layer == gfx->num_layers)
        {
            int new_num_layers = layer + 1;
            if(gfx->layer_tags)
                gfx->layer_tags = resizebytes(gfx->layer_tags, sizeof(char*) * gfx->num_layers, sizeof(char*) * new_num_layers);
            else
                gfx->layer_tags = getbytes(sizeof(char*));
            
            gfx->layer_tags[layer] = getbytes(64);
            snprintf(gfx->layer_tags[layer], 64, ".l%i%lx", layer, (long)obj);
            gfx->num_layers = new_num_layers;
        }

For now, I'm going with solution 2, because I feel like this actually solves the problem instead of trying to make an invalid state work. But feel free to work on this if you have the time.

@agraef
Copy link
Owner

agraef commented Oct 5, 2024

Hope that helps, let me know if you spot anywhere I did it wrong.

No, it makes perfect sense now, thanks for explaining it once more and sorry for my slow-wittedness. :)

It will try to position layer 3 on top of layer 2, but tcl/tk is not aware layer 2 exists yet, so it will print an error. So we need to find a way around that too.

Right, I hadn't thought about this bit, because that raise command doesn't exist in the PURR_DATA code. I can do without it, because I'm just replacing the inner html in the DOM when repainting the object, which maintains the correct stacking order without any further ado.

Of course, with the layer implementation, I will now have to deal with that kind of situation in the PURR_DATA code, too. But I can actually solve this by simply adding the SVG equivalent of what you called a skipped layer (solution 1) right there in start_paint(). In HTML/SVG this is just an empty SVG group which may be filled with graphics objects later if and when the layer actually gets painted. This makes it possible to maintain the correct stacking order without having to resort to a full repaint.

It would be awesome if something like this could be implemented in vanilla's Tcl/Tk GUI as well, but I can see why you're running into toolkit limitations with this. :( While I still like Tcl/Tk for its simplicity, it shows its age there. Having the DOM and HTML+SVG rendering engine of a modern HTML browser at your fingertips makes all this much easier.

Ok, I think that I can continue with the purr-data port now. I haven't got very far with it yet, but I think that I have a good grasp of the layer architecture now, so this should be easy to do.

pdlua_gfx.h Show resolved Hide resolved
pd.lua Show resolved Hide resolved
pdlua_gfx.h Show resolved Hide resolved
@agraef
Copy link
Owner

agraef commented Oct 5, 2024

Ok, I think that this will do: d0dd9a7...496b965

Great job, Tim, as always! Porting this for purr-data was surprisingly easy, congrats on the excellent design!

For the record: In the purr-data implementation, this just required adding the layer management in start_paint, and adjusting pdlua_gfx_clear accordingly. The graphics operations are virtually unchanged, except that they now take the layer tag in lieu of the object tag as argument. And in the purr-data pdgui.js module, I just needed to add a single new gui_luagfx_new_layer function which takes care of creating the layers (a.k.a. svg 'g' objects) which act as containers for the svg graphics objects now. And this should be entirely backward-compatible, just like it is in vanilla -- with older pd-lua versions, it will just continue to use the gobj as parent instead of the layers and never call gui_luagfx_new_layer at all. At least that's what I hope. :)

I still need to test this, which I'll do over the weekend. In particular, I need to make sure that the generated layer tags don't collide with tags we already use in the purr-data GUI, but that seems unlikely.

Alright, I'm going to merge this now!

@agraef agraef merged commit 480cd2a into agraef:master Oct 5, 2024
10 checks passed
@agraef
Copy link
Owner

agraef commented Oct 5, 2024

All done and dusted, 0.12.19 is tagged and currently building.

I still had to fix a few glitches and updated the docs, so Tim, please make sure that you merge back the latest into your fork.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants