Skip to content

Commit

Permalink
pdx.lua: Add prereload/postreload callback methods (experimental).
Browse files Browse the repository at this point in the history
These methods are invoked when an object script gets reloaded, so that
you can customize the reload process. This was suggested by @ben-wes,
thanks Ben!

- self:prereload(), if it exists, gets invoked immediately *before*
  reloading the script file.

- self:postreload(), if it exists, gets invoked immediately *after*
  reloading the script file.

Both prereload() and postreload() may change the member variables of the
object in any desired way. The only difference is that prereload() still
runs in the *old* script, while postreload() uses the *new* script which
has just been loaded. Thus, prereload() would typically be used to save
some essential state which might be lost during reloading, so that it
can be retrieved later (e.g., in the postreload() method).

Note that these methods can, in particular, change the inlets and
outlets members of the object. In this case, after running the
postreload() method, the object's iolets will be adjusted accordingly.

To make this work, we modified the internal inlet/outlet creation
routines in pdlua.c so that they will update the iolets (instead of
always creating them from scratch), while preserving as many existing
connections as possible. Note that this may still cause some cords to be
removed, if the new configuration has less iolets or iolets of different
(control/signal) types.
  • Loading branch information
agraef committed Sep 1, 2024
1 parent 14524b2 commit 84bb700
Show file tree
Hide file tree
Showing 2 changed files with 161 additions and 63 deletions.
168 changes: 113 additions & 55 deletions pdlua.c
Original file line number Diff line number Diff line change
Expand Up @@ -1272,37 +1272,71 @@ static int pdlua_object_createinlets(lua_State *L)
{
t_pdlua *o = lua_touserdata(L, 1);
if(o) {
if (lua_isnumber(L, 2)) {
// If it's a number, it means the number of data inlets
o->inlets = luaL_checknumber(L, 2);
o->proxy_in = malloc(o->inlets * sizeof(t_pdlua_proxyinlet));
o->in = malloc(o->inlets * sizeof(t_inlet*));
for (int i = 0; i < o->inlets; ++i)
{
/* This is complicated, because we also need to update existing
inlets if we created them before. To these ends, we need to get
rid of any old inlets that aren't needed anymore, and remove
cords going into these, as well as cords for the remaining
inlets if their type (control or signal) has changed. Failing
to do so would cause Pd to crash. */
// record the number of old inlets
int old_inlets = o->inlets;
// determine the new number of inlets
int have_number = lua_isnumber(L, 2);
int have_table = lua_istable(L, 2);
int new_inlets = have_number ? luaL_checknumber(L, 2) :
have_table ? lua_rawlen(L, 2) : 0;
if (!have_number && !have_table) return luaL_error(L, "inlets must be a number or a table");
// need to suspend dsp state here, in case any signal inlets are
// created or destroyed
int dspstate = canvas_suspend_dsp();
// check whether we need to redraw iolets and cords
int redraw = o->pd.te_binbuf && gobj_shouldvis(&o->pd.te_g, o->canvas) && glist_isvisible(o->canvas);
if (redraw) {
gobj_vis(&o->pd.te_g, o->canvas, 0);
}
// remove the excess inlets; note that we need to keep the other
// inlets around for now, because we may have to remove their
// cords later
for (int i = new_inlets; i < old_inlets; ++i) {
canvas_deletelinesforio(o->canvas, (t_text*)o, o->in[i], 0);
inlet_free(o->in[i]);
}
// create the new inlets
o->inlets = new_inlets;
o->proxy_in = realloc(o->proxy_in, new_inlets * sizeof(t_pdlua_proxyinlet));
o->in = realloc(o->in, new_inlets * sizeof(t_inlet*));
o->siginlets = 0;
for (int i = 0; i < new_inlets; ++i) {
if (i >= old_inlets)
// add a new proxy
pdlua_proxyinlet_init(&o->proxy_in[i], o, i);
o->in[i] = inlet_new(&o->pd, &o->proxy_in[i].pd, 0, 0);
}
} else if (lua_istable(L, 2)) {
// If it's a table, it means a list of inlet types (data or signal)
o->inlets = lua_rawlen(L, 2);
o->proxy_in = malloc(o->inlets * sizeof(t_pdlua_proxyinlet));
o->in = malloc(o->inlets * sizeof(t_inlet*));
for (int i = 0; i < o->inlets; ++i)
{
int is_signal = 0;
if (have_table) {
lua_rawgeti(L, 2, i + 1); // Get element at index i+1
if (lua_isnumber(L, -1)) {
int is_signal = lua_tonumber(L, -1);
o->siginlets += is_signal;

pdlua_proxyinlet_init(&o->proxy_in[i], o, i);
o->in[i] = inlet_new(&o->pd, &o->proxy_in[i].pd, is_signal ? &s_signal : 0, is_signal ? &s_signal : 0);
}
if (lua_isnumber(L, -1))
// this should be a 0-1 flag, convert from a Lua
// number which is some kind of float
is_signal = !!(int)lua_tonumber(L, -1);
lua_pop(L, 1); // Pop the value from the stack
}
} else {
// Invalid argument type
return luaL_error(L, "inlets must be a number or a table");
o->siginlets += is_signal;
if (i < old_inlets) {
int old_issignal = obj_issignalinlet(&o->pd, i);
if (is_signal != old_issignal)
// need to remove all cords
canvas_deletelinesforio(o->canvas, (t_text*)o, o->in[i], 0);
// get rid of the old inlet now, it will be recreated below
inlet_free(o->in[i]);
}
t_symbol *sym = is_signal ? &s_signal : 0;
o->in[i] = inlet_new(&o->pd, &o->proxy_in[i].pd, sym, sym);
}
if (redraw) {
// force object and its iolets to be redrawn
gobj_vis(&o->pd.te_g, o->canvas, 1);
canvas_fixlinesfor(o->canvas, (t_text*)o);
}
canvas_resume_dsp(dspstate);
}

}
Expand All @@ -1324,37 +1358,61 @@ static int pdlua_object_createoutlets(lua_State *L)
t_pdlua *o = lua_touserdata(L, 1);
if (o)
{
if (lua_isnumber(L, 2)) {
// If it's a number, it means the number of data outlets
o->outlets = luaL_checknumber(L, 2);
if (o->outlets > 0)
{
o->out = malloc(o->outlets * sizeof(t_outlet *));
for (int i = 0; i < o->outlets; ++i) o->out[i] = outlet_new(&o->pd, 0);
// record the number of old outlets
int old_outlets = o->outlets;
// determine the new number of outlets
int have_number = lua_isnumber(L, 2);
int have_table = lua_istable(L, 2);
int new_outlets = have_number ? luaL_checknumber(L, 2) :
have_table ? lua_rawlen(L, 2) : 0;
if (!have_number && !have_table) return luaL_error(L, "outlets must be a number or a table");
// need to suspend dsp state here, in case any signal outlets are
// created or destroyed
int dspstate = canvas_suspend_dsp();
// check whether we need to redraw iolets and cords
int redraw = o->pd.te_binbuf && gobj_shouldvis(&o->pd.te_g, o->canvas) && glist_isvisible(o->canvas);
if (redraw) {
gobj_vis(&o->pd.te_g, o->canvas, 0);
}
// remove the excess outlets; note that we need to keep the other
// outlets around for now, because we may have to remove their
// cords later
for (int i = new_outlets; i < old_outlets; ++i) {
canvas_deletelinesforio(o->canvas, (t_text*)o, 0, o->out[i]);
outlet_free(o->out[i]);
}
// create the new outlets
o->outlets = new_outlets;
o->out = realloc(o->out, new_outlets * sizeof(t_outlet*));
o->sigoutlets = 0;
for (int i = 0; i < new_outlets; ++i) {
int is_signal = 0;
if (have_table) {
lua_rawgeti(L, 2, i + 1); // Get element at index i+1
if (lua_isnumber(L, -1))
// this should be a 0-1 flag, convert from a Lua
// number which is some kind of float
is_signal = !!(int)lua_tonumber(L, -1);
lua_pop(L, 1); // Pop the value from the stack
}
else o->out = NULL;
} else if (lua_istable(L, 2)) {
// If it's a table, it means a list of outlet types (data or signal)
o->outlets = lua_rawlen(L, 2);
if (o->outlets > 0)
{
o->out = malloc(o->outlets * sizeof(t_outlet *));
for (int i = 0; i < o->outlets; ++i)
{
lua_rawgeti(L, 2, i + 1); // Get element at index i+1
if (lua_isnumber(L, -1)) {
int is_signal = lua_tonumber(L, -1);
o->sigoutlets += is_signal;
o->out[i] = outlet_new(&o->pd, is_signal ? &s_signal : 0);
}
lua_pop(L, 1); // Pop the value from the stack
}
o->sigoutlets += is_signal;
if (i < old_outlets) {
int old_issignal = obj_issignaloutlet(&o->pd, i);
if (is_signal != old_issignal)
// need to remove all cords
canvas_deletelinesforio(o->canvas, (t_text*)o, 0, o->out[i]);
// get rid of the old outlet now, it will be recreated below
outlet_free(o->out[i]);
}
else o->out = NULL;
} else {
// Invalid argument type
return luaL_error(L, "outlets must be a number or a table");
t_symbol *sym = is_signal ? &s_signal : 0;
o->out[i] = outlet_new(&o->pd, sym);
}
if (redraw) {
// force object and its iolets to be redrawn
gobj_vis(&o->pd.te_g, o->canvas, 1);
canvas_fixlinesfor(o->canvas, (t_text*)o);
}
canvas_resume_dsp(dspstate);

}
}
Expand Down Expand Up @@ -1561,7 +1619,7 @@ static int pdlua_object_free(lua_State *L)
o->in = NULL;
}

if (o->proxy_in) freebytes(o->proxy_in, sizeof(struct pdlua_proxyinlet) * o->inlets);
if (o->proxy_in) free(o->proxy_in);

if(o->out)
{
Expand Down
56 changes: 48 additions & 8 deletions pdlua/tutorial/examples/pdx.lua
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,12 @@ pdx.reload installs a "pdluax" receiver which reloads the object's script file
when it receives the "reload" message without any arguments, or a "reload
class" message with a matching class name.
Before reloading the script, we also execute the object's prereload() method if
it exists, and the postreload() method after reloading, so that the user can
customize object reinitialization as needed (e.g., to call initialize() after
reloading, or to perform some other custom reinitialization). This was
suggested by @ben-wes (thanks Ben!).
We have to go to some lengths here since we only want the script to be
reloaded *once* for each class, not for every object in the class. This
complicates things quite a bit. In particular, we need to keep track of object
Expand Down Expand Up @@ -71,14 +77,48 @@ local function pdluax(self, sel, atoms)
if sel == "reload" then
-- reload message, check that any extra argument matches the class name
if atoms[1] == nil or atoms[1] == self._name then
pd.post(string.format("pdx: reloading %s", self._name))
self:dofilex(self._scriptname)
-- update the object's finalizer and restore our own, in case
-- anything has changed there
if self.finalize ~= finalize then
reloadables[self._name][self].finalize = self.finalize
self.finalize = finalize
end
-- invoke the prereload method if it exists
if self.prereload and type(self.prereload) == "function" then
self:prereload()
end
pd.post(string.format("pdx: reloading %s", self._name))
self:dofilex(self._scriptname)
-- update the object's finalizer and restore our own, in case
-- anything has changed there
if self.finalize ~= finalize then
reloadables[self._name][self].finalize = self.finalize
self.finalize = finalize
end
-- invoke the postreload method if it exists
if self.postreload and type(self.postreload) == "function" then
local inlets, outlets = self.inlets, self.outlets
self:postreload()
-- recreate inlets and outlets as needed
local function iolets_eq(a, b)
if type(a) ~= type(b) then
return false
elseif type(a) == "table" then
if #a ~= #b then
return false
else
for i = 1, #a do
if a[i] ~= b[i] then
return false
end
end
return true
end
else
return a == b
end
end
if not iolets_eq(self.inlets, inlets) then
pd._createinlets(self._object, self.inlets)
end
if not iolets_eq(self.outlets, outlets) then
pd._createoutlets(self._object, self.outlets)
end
end
end
end
end
Expand Down

0 comments on commit 84bb700

Please sign in to comment.