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
toolkit-agnostic Core->GUI Communication - WIP #1763
Conversation
…o refactor all the "delete" calls to the pdgui_vmess() in a single place
…ses (i.e.: to produce consistent logs that are convenient to compare with pure-data-gui-tester and not thoroughly tested)
…(i.e.: to produce consistent logs that are convenient to compare with pure-data-gui-tester) and should not be used in production without a switch to manually enable it
…to pd_connect::pd_docmds as needed. Currently it can create 'obj'.
…e from bng_draw_new only
…() calls. Not sure it's a good idea to have this, maybe we should have classes for each C GUI class, each with its customised protocol
…E_PDTK_CANVAS_PROC/g" src/*.c
…m, whose handling has also been refactored
proc ::pdtk_canvas::create {args} { | ||
#puts "pdtk_canvas::create got: $args" | ||
set docmds "" | ||
check_argc_least 2 $args |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is there any specific reason to manually check the number of arguments rather than doing proc ::pdtk_canvas::create {type args} { ... }
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
not really
thanks. while it's too late here to do a full review (or even to read your lengthy text), here's some comments: basic information about "self" on the GUI-sidei would actually prefer if the object-representation on the GUI side would know "what it is". e.g. after issuing a |
GUI: widgetbehaviouri find these switch/case like something like this (for the sake of simplicity, this misses error handling and fallbacks): # public interface for Pd->GUI communication
proc ::pdtk_canvas::register_object {type ctor} {
set ::pdtk_canvas::procs::constructor($type) $ctor
}
proc ::pdtk_canvas::create {type canvas obj) {
$::pdtk_canvas::procs::constructor($type) $canvas $obj
}
proc ::pdtk_canvas::select {obj) {
$::pdtk_canvas::procs::select($obj) $obj
}
## implementation of [bng]
proc ::pdtk_canvas::bang::create {canvas obj} {
set ::pdtk_canvas::procs::select($obj) ::pdtk_canvas::bang::select
}
proc ::pdtk_canvas::bang::select {obj} {
# ...
}
::pdtk_canvas::register_object "bang" ::pdtk_canvas::bang::create |
namespacepersonally i would go with a different namespace, e.g. of course there's the precedent of what is already there, but i don't see any reason not to use nicer names for new functions... |
this is entirely on purpose. also, the backend shouldn't need to deal with tags at all (the mere existence of a tag-concept is a frontend implementation detail). |
in the end, i think we should be more radical about replacing things. we probably do not want to end up with 3 different ways to interface with the GUI (the "traditional" one with sending raw tcl commands; a "new" one that abstracts away the gory details; and one intermediate one). (of course, your code is just an experiment for now to see what would happen; so this doesn't really apply; i just wanted to stress it...) |
Sorry I don't know enough about how canvases and glist work in Pd to understand where to get the "list of all $cnv'ses", so for I have only been passing the canvas that I find in the current calls to
OK, I had intentionally steered away from that because the various calls I replaced with
OK, so the GUI will keep some state (currently it's entirely stateless). IIUC we should store in a dict the
Good to keep that in mind, it makes a lot of sense. Do I understand correctly then that you are suggesting we should only pass the cnv and obj_id to the frontend which should then be able to generate all the needed tags? I see a few places where this gets ugly ... For instance, for obj/*atom/listbox/msg/commentbar, (afaict) the Anyhow, I think I should be able to have a go at addressing all of your observations above, but I guess I'll wait for any further observations first. |
I've said it before, but all of the current 'editor' functionality should be done in tcl/tk imo. There should hardly ever be a need to send a 'move' from the core to the gui; the gui should do most of that itself. It would just send 'connection' messages to pd to tell it which objects to connect or disconnect. & the 'widgetbehavior's should also live on the gui side.. |
we hear you. moving "all of the current 'editor' functionality" is summerized in workflow#6 ( i'm afraid, it's not high priority for now. |
I would frame it a bit differently: there should be the possibility for any GUI backend to completely circumvent the Pd editor, especially all the collision detection. I think this has been on the roadmap as one of the end goals, but now I can't find it... |
@Spacechild1 well, for 'canonical' pd editor that could be the case as well.. all done in tcl/tk (maybe w/ a custom tcl/tk lib if needed). I don't see why any editor logic should be on the audio thread.. I only mention it again now bc it seemed like there was some discussion of implementing changes incrementally vs. not, but of course it's a matter of degree.. |
well, tcl/tk would be a horrible language to try to write the whole editor in. Just the undo/redo stuff would be horribly difficult to write and debug in a script language. OTOH I can imagine doing hit detection up in the GUI - I think that would improve things a good deal, and it wouldn't have to imply putting any real smarts up there. |
I think the big issue with a super-thin GUI is, that we end up with a lot of assumptions on how the GUI works in the audio core.
so the big task at hand is to identify these assumptions and remove them from the Pd-core. i think this will inevitably move some logic to the GUI side, thus making it "more intelligent". |
I did not rule out the 'canonical' Pd GUI. However, the Tcl/Tk GUI is already very slow - moving a single object on a non-trivial patch can easily max out a whole CPU core - and moving hit detection to Tcl/Tk could make the GUI almost unusable. Of course, this needs to be tested. That's why the end goal should be about the possibility to move all (or some) of the editor logic to the GUI side. Each GUI implementation should be able to decide if it wants to implement the editor logic - or not. For example, if I were to write an alternative GUI with Qt, all the editor logic would naturally go to the GUI side because Qt's canvas implementation is extremely optimized and offers all the things we need out of the box. |
Btw, the whole roadmap currently resides in an (accidentally) closed PR (#1693). Should we rather make this a Discussion item? |
are you sure that this is Tcl/Tk, or the way how Pd uses Tcl/Tk? |
I'm pretty sure it's because Tk's canvas implementation is not optimized and apparently does lots and lots of unnecessary redrawing... All I'm doing is moving a single object in a non-trivial patch. |
how non-trivial? i just did a quick test with 10000 rectangles, 10000 ovals and 10000 text labels on a single canvas. #!/usr/bin/env wish
proc make_bang {cnv tag x y} {
$cnv create rectangle 0 0 20 20 -tags [list ${tag} ${tag}_R bang bang_R]
$cnv create oval 2 2 18 18 -tags [list ${tag} ${tag}_C bang bang_C] -fill yellow
$cnv create text 0 0 -text $tag -tags [list ${tag} ${tag}_T bang bang_T]
$cnv move "${tag}" $x $y
}
foreach {::x ::y} {{} {}} {break}
proc moveme {cnv x y {tag all}} {
if { ${::x} eq {} } {set ::x $x}
if { ${::y} eq {} } {set ::y $y}
$cnv move $tag [expr $x - $::x] [expr $y - $::y]
foreach {::x ::y} [list $x $y] {break}
}
canvas .c -width 1000 -height 1000 -background white
pack .c
for {set i 0} {$i < 10240} {incr i} {
make_bang .c "bang_${i}" [expr rand()*2000 - 1000] [expr rand()*2000 - 1000]
}
make_bang .c selected 500 500
bind .c <Motion> [list moveme %W %x %y all] this is not neglectible, but it's not that horrible either (i noticed no lag) EDIT: to give more specs about my test setup. this was done with an "11th Gen Intel(R) Core(TM) i5-1145G7 @ 2.60GHz" CPU with an "Intel Corporation TigerLake-LP GT2 [Iris Xe Graphics]" gfx card (stock Xfce drivers; but properietary firmware loaded). the Tcl/Tk process was forced to a single CPU, and I tried to set it to a fixed clockrate (but miserably failed; in the end the clock rate was between 4GHz and 4.4GHz). XFce4 used another 70% of another core. |
Wondering if it's system-dependent. It's pretty bad on my macbook pro (100% CPU and noticeable lag) using macos 10.14's Wish (tcl 8.5) or another Wish I have (tcl 8.6). This is with an Intel CPU fwiw. Taking it down to 1000 bangs still gets 100% CPU. 100 bangs is more like 17%. |
@giuliomoro yes there's something suspicious w/ mac's tcl/tk implementation recently haha (see: #1488 and https://core.tcl-lang.org/tk/tktview/f642d7c0f4 that doesn't seem resolved, and doesn't seem like there's interest in resolving things..). maybe 'canonical' pd should switch gui frameworks amongst these 'big' changes..Qt or wxwidgits or juce or something |
I've tried the "10000 bangs" test on my "old " AMD Phenom(tm) II X4 970 (still 3.5GHz quad core) on Ubuntu 16.04. The window takes ~8s to be fully created. Maybe gfx card/driver related (my AMD CYPRESS is not supported by the AMD proprietary driver anymore, so it falls back to the open-source radeon X driver)? |
I think it is all software rendering (which is not a problem in itself if done properly). |
we might want to discuss this in a separate issue, as it seems to be unrelated to the actual PR
|
superseded by #1765 |
is it? |
|
I have done a first pass at implementing some tcl procs as discussed in #1695 to move away from passing tk commands. It is my first time at tcl and I don't have a deep understanding of the inner works of Pd, so please bare with me if anything about the style is not ideal. I
#define USE_PDTK_CANVAS_PROC
in any relevant file. This should be done through configure, but honestly this is easier for developing/debugging so I can switch it off/on per file as needed.The code can be found here https://github.com/giuliomoro/pure-data-1/tree/1695-tests
I made a few NIT commits to begin with which helped me minimse the
diff
later on. Two commits are meant to be reverted later on as they are used only to log the actual docmds executed by the frontend in such a way that they can be used for automated testing:a2f07d9 tcl: print docmds as they get executed. This is for testing purposes (i.e.: to produce consistent logs that are convenient to compare with pure-data-gui-tester) and should not be used in production without a switch to manually enable it
f227d05 tcl: evaluate one command at a time. This is mainly for testing purposes (i.e.: to produce consistent logs that are convenient to compare with pure-data-gui-tester and not thoroughly tested)
Throughout, I have been testing this with my pure-data-gui-testing, whose test patch has been extended to include coverage to all (I think) the tcl calls that have been affected by the commits on this branch (incl e.g.: deleting, selecting and moving objects). Where appropriate, I added workarounds in the tcl code to ensure that the tcl command being generated is exactly the same as in the code I am comparing against (a2f07d9). These can be removed at a later date.
What has been migrated:
pdtk_text_*
procedures.pdtk_text_*
I tried to stay reasonably close to the existing C functions. This means that I have all of the following procs on the tcl side: config create delete iemgui_label_font iemgui_label_pos move select update. This could surely be rationalised further (e.g.: config and update I think are only iemgui-specific). However in some cases I rationalised it a bit more. E.g.:
text_drawborder()
'sfirsttime
flag distinguishes between what semantically are acreate
and amove
operation and so I made that explicit in the code.I am minimising the number of arguments to be passed to the GUI, but still recreate the same pixel-accurate drawing. E.g.: creating a
T_OBJECT
passes the x1,y1,x2,y2 coordinates, creating aT_ATOM
(float or symbol) passes those pluscorner
. In order to allow this, a minimal amount of the logic that is in the C data which is needed to draw the shapes from this data has been duplicated in tcl (seeget_poly_coords()
). Another example of what is needed to minimise the number of parameters passed is that only the obj's ID (ptr) is passed to the GUI in several bang methods, where it is then manipulated to generate the various OBJ, BASE, BUT, LABEL tags (see bang_get_tags()). On this note,pdgui_vmess
doesn't seem to allow to pass a plain%lx
which would be needed for this case, but only%p
or.x%lx
, so I have to trim the leading0x
from the argument in the GUI. It would be great if a type could be added that allows us to pass%lx
without further processing needed on the front end.I am currently not following the recommendation above
This is in the first instance because I tried to keep the same argument order as the
pdgui_vmess()
calls I am replacing (weeding out unnecessary arguments), in order to facilitate my work. Furthermore, most of these calls (all except forcreate
) do not really have a "stored on the disk" counterpart. The parameters I am passing tocreate
include more details than what is stored to disk. E.g.: in the case above offloatatom
with a-width
of5
, the actual box's coordinates are computed in the backed based on font size and "width". This logic could be moved to the frontend if desired but it's not there yet. To complicate the matter further (at least for me), in order to send the object name's (or analogously the atom's or message's or comment's content) alongside thecreate()
call would need some refactoring on the core side, as::create()
is called e.g.: in text_drawborder() while the text is set created inrtext_senditup()
. Also keep in mind that if the call tocreate()
looks more similar to how the object is stored on disk (e.g.: passing x0 and y0 coordinates and text), then the computation of the size of the box (rtext_width()
,rtext_height()
) moves to the frontend and so the hit detection problem manifests itself and needs to be dealt with.Before doing any further work, I wanted to stop and check in first. Feel free to comment on all of this and the code: this was mostly a time-limited attempt I made in order for me to better understand the issues at hand, but I am not strongly attached to any of the code I wrote.
I thought I'd make a comment here instead of opening a PR because this is veeery far from being mergeable and this is more of a contribution to the discussion that is ongoing here, but let me know if a PR would be a better approach for this and I can do that.
Not sure whether the conversation is best continued her or on #1695