Skip to content

Commit

Permalink
Reduce clicking when creating objects
Browse files Browse the repository at this point in the history
  • Loading branch information
timothyschoen committed Nov 28, 2024
1 parent b3a8f33 commit 29bab25
Show file tree
Hide file tree
Showing 7 changed files with 119 additions and 69 deletions.
4 changes: 4 additions & 0 deletions Source/Canvas.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1109,6 +1109,9 @@ void Canvas::synchroniseSplitCanvas()
// Used for loading and for complicated actions like undo/redo
void Canvas::performSynchronise()
{
// By flushing twice, we can make sure that any message sent before this point will be dequeued
pd->doubleFlushMessageQueue();

// Remove deleted connections
for (int n = connections.size() - 1; n >= 0; n--) {
if (!connections[n]->getPointer()) {
Expand Down Expand Up @@ -1240,6 +1243,7 @@ void Canvas::performSynchronise()
needsSearchUpdate = true;

pd->updateObjectImplementations();
cancelPendingUpdate(); // if an update got retriggered, cancel it
}

void Canvas::updateDrawables()
Expand Down
13 changes: 9 additions & 4 deletions Source/Object.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -615,6 +615,11 @@ void Object::updateTooltips()
SmallArray<std::pair<int, String>, 16> inletMessages;
SmallArray<std::pair<int, String>, 16> outletMessages;

auto* inletSym = cnv->pd->generateSymbol("inlet");
auto* inletTildeSym = cnv->pd->generateSymbol("inlet~");
auto* outletSym = cnv->pd->generateSymbol("outlet");
auto* outletTildeSym = cnv->pd->generateSymbol("outlet~");

if (auto subpatch = gui->getPatch()) {
cnv->pd->lockAudioThread();
auto* subpatchPtr = subpatch->getPointer().get();
Expand All @@ -624,10 +629,10 @@ void Object::updateTooltips()

if (!obj.isValid())
continue;

auto const name = hash(pd::Interface::getObjectClassName(obj.getRaw<t_pd>()));
auto* name = (*obj.getRaw<t_pd>())->c_name;
auto* checkedObject = pd::Interface::checkObject(obj.getRaw<t_pd>());
if (name == hash("inlet") || name == hash("inlet~")) {
if (name == inletSym || name == inletTildeSym) {
int size;
char* str_ptr;
pd::Interface::getObjectText(checkedObject, &str_ptr, &size);
Expand All @@ -639,7 +644,7 @@ void Object::updateTooltips()
auto const text = String::fromUTF8(str_ptr, size);
inletMessages.emplace_back(x, text.fromFirstOccurrenceOf(" ", false, false));
freebytes(static_cast<void*>(str_ptr), static_cast<size_t>(size) * sizeof(char));
} else if (name == hash("outlet") || name == hash("outlet~")) {
} else if (name == outletSym || name == outletTildeSym) {
int size;
char* str_ptr;
pd::Interface::getObjectText(checkedObject, &str_ptr, &size);
Expand Down
57 changes: 31 additions & 26 deletions Source/Objects/ImplementationBase.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -128,52 +128,57 @@ ObjectImplementationManager::ObjectImplementationManager(pd::Instance* processor

void ObjectImplementationManager::handleAsyncUpdate()
{
SmallArray<std::pair<t_canvas*, t_canvas*>> allCanvases;
SmallArray<std::pair<t_canvas*, t_gobj*>> allImplementations;
UnorderedSet<t_gobj*> allObjects;

pd->setThis();

pd->lockAudioThread();
for (auto* topLevelCnv = pd_getcanvaslist(); topLevelCnv; topLevelCnv = topLevelCnv->gl_next) {
allCanvases.add({ topLevelCnv, topLevelCnv });
getSubCanvases(topLevelCnv, topLevelCnv, allCanvases);
}

for (auto& [top, glist] : allCanvases) {
for (t_gobj* y = glist->gl_list; y; y = y->g_next) {
auto const* name = pd::Interface::getObjectClassName(&y->g_pd);
if (ImplementationBase::hasImplementation(name)) {
allImplementations.add({ top, y });
allObjects.insert(y);
}
}
}
pd->unlockAudioThread();


objectImplementationLock.enter();
for (auto it = objectImplementations.cbegin(); it != objectImplementations.cend();) {
auto& [ptr, implementation] = *it;

if (allObjects.contains(ptr)) {
if (targetObjects.contains(ptr)) {
it = objectImplementations.erase(it); // Erase and move iterator forward.
} else {
++it;
}
}

for (auto& [cnv, obj] : allImplementations) {
for (auto& [cnv, obj] : targetImplementations) {
if (!objectImplementations.count(obj)) {
auto const name = String::fromUTF8(pd::Interface::getObjectClassName(&obj->g_pd));
objectImplementations[obj] = std::unique_ptr<ImplementationBase>(ImplementationBase::createImplementation(name, obj, cnv, pd));
}

objectImplementations[obj]->update();
}
objectImplementationLock.exit();
}

void ObjectImplementationManager::updateObjectImplementations()
{
triggerAsyncUpdate();
pd->enqueueFunctionAsync([this](){
SmallArray<std::pair<t_canvas*, t_canvas*>> allCanvases;
SmallArray<std::pair<t_canvas*, t_gobj*>> allImplementations;
UnorderedSet<t_gobj*> allObjects;

for (auto* topLevelCnv = pd_getcanvaslist(); topLevelCnv; topLevelCnv = topLevelCnv->gl_next) {
allCanvases.add({ topLevelCnv, topLevelCnv });
getSubCanvases(topLevelCnv, topLevelCnv, allCanvases);
}

for (auto& [top, glist] : allCanvases) {
for (t_gobj* y = glist->gl_list; y; y = y->g_next) {
auto const* name = pd::Interface::getObjectClassName(&y->g_pd);
if (ImplementationBase::hasImplementation(name)) {
allImplementations.add({ top, y });
allObjects.insert(y);
}
}
}

objectImplementationLock.enter();
targetImplementations = allImplementations;
targetObjects = allObjects;
objectImplementationLock.exit();
triggerAsyncUpdate();
});
}

void ObjectImplementationManager::getSubCanvases(t_canvas* top, t_canvas* canvas, SmallArray<std::pair<t_canvas*, t_canvas*>>& allCanvases)
Expand Down
3 changes: 3 additions & 0 deletions Source/Objects/ImplementationBase.h
Original file line number Diff line number Diff line change
Expand Up @@ -51,4 +51,7 @@ class ObjectImplementationManager : public AsyncUpdater {
PluginProcessor* pd;

UnorderedMap<t_gobj*, std::unique_ptr<ImplementationBase>> objectImplementations;
SmallArray<std::pair<t_canvas*, t_gobj*>> targetImplementations;
UnorderedSet<t_gobj*> targetObjects;
CriticalSection objectImplementationLock;
};
103 changes: 64 additions & 39 deletions Source/Objects/ObjectBase.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -537,21 +537,30 @@ ObjectBase* ObjectBase::createGui(pd::WeakReference ptr, Object* parent)
parent->cnv->pd->setThis();

// This will ensure the object is still valid at this point, and also locks the audio thread to make sure it will remain valid
hash32 name;
bool isNonPatchable = false;
if (auto checked = ptr.get<t_gobj>()) {
auto const name = hash(pd::Interface::getObjectClassName(checked.cast<t_pd>()));
name = hash(pd::Interface::getObjectClassName(checked.cast<t_pd>()));
isNonPatchable = !pd::Interface::checkObject(checked.get());
}
else {
return nullptr;
}

if (parent->cnv->pd->isLuaClass(name)) {
if (parent->cnv->pd->isLuaClass(name)) {
if (auto checked = ptr.get<t_gobj>()) {
if (checked.cast<t_pdlua>()->has_gui) {
return new LuaObject(ptr, parent);
} else {
return new LuaTextObject(ptr, parent);
}
}
// check if object is a patcher object, or something else
if (!pd::Interface::checkObject(checked.get()) && name != hash("scalar")) {
return new NonPatchable(ptr, parent);
} else {
switch (name) {
}
// check if object is a patcher object, or something else
if (isNonPatchable && name != hash("scalar")) {
return new NonPatchable(ptr, parent);
} else {
switch (name) {
case hash("bng"):
return new BangObject(ptr, parent);
case hash("button"):
Expand All @@ -574,16 +583,25 @@ ObjectBase* ObjectBase::createGui(pd::WeakReference ptr, Object* parent)
case hash("vu"):
return new VUMeterObject(ptr, parent);
case hash("text"): {
if (checked.cast<t_text>()->te_type == T_OBJECT) {
unsigned int type;
if (auto checked = ptr.get<t_text>()) {
type = checked->te_type;
}
else {
break;
}
if (type == T_OBJECT) {
return new TextObject(ptr, parent, false);
} else {
return new CommentObject(ptr, parent);
}
}
// Check if message type text object to prevent confusing it with else/message
// Check if message type text object to prevent confusing it with else/message
case hash("message"): {
if (pd::Interface::isTextObject(checked.get()) && checked.cast<t_text>()->te_type == T_MESSAGE) {
return new MessageObject(ptr, parent);
if (auto checked = ptr.get<t_gobj>()) {
if (pd::Interface::isTextObject(checked.get()) && checked.cast<t_text>()->te_type == T_MESSAGE) {
return new MessageObject(ptr, parent);
}
}
break;
}
Expand All @@ -599,31 +617,35 @@ ObjectBase* ObjectBase::createGui(pd::WeakReference ptr, Object* parent)
case hash("qlist"):
return new TextFileObject(ptr, parent);
case hash("gatom"): {
if (checked.cast<t_fake_gatom>()->a_flavor == A_FLOAT) {
return new FloatAtomObject(ptr, parent);
} else if (checked.cast<t_fake_gatom>()->a_flavor == A_SYMBOL) {
return new SymbolAtomObject(ptr, parent);
} else if (checked.cast<t_fake_gatom>()->a_flavor == A_NULL) {
return new ListObject(ptr, parent);
if (auto checked = ptr.get<t_gobj>()) {
if (checked.cast<t_fake_gatom>()->a_flavor == A_FLOAT) {
return new FloatAtomObject(ptr, parent);
} else if (checked.cast<t_fake_gatom>()->a_flavor == A_SYMBOL) {
return new SymbolAtomObject(ptr, parent);
} else if (checked.cast<t_fake_gatom>()->a_flavor == A_NULL) {
return new ListObject(ptr, parent);
}
}
break;
}
case hash("canvas"):
case hash("graph"): {
auto* canvas = checked.cast<t_canvas>();
if (checked.cast<t_canvas>()->gl_list) {
t_class* c = canvas->gl_list->g_pd;
if (c && c->c_name && (String::fromUTF8(c->c_name->s_name) == "array")) {
return new ArrayObject(ptr, parent);
if (auto checked = ptr.get<t_gobj>()) {
auto* canvas = checked.cast<t_canvas>();
if (checked.cast<t_canvas>()->gl_list) {
t_class* c = canvas->gl_list->g_pd;
if (c && c->c_name && (String::fromUTF8(c->c_name->s_name) == "array")) {
return new ArrayObject(ptr, parent);
} else if (canvas->gl_isgraph) {
return new GraphOnParent(ptr, parent);
} else { // abstraction or subpatch
return new SubpatchObject(ptr, parent);
}
} else if (canvas->gl_isgraph) {
return new GraphOnParent(ptr, parent);
} else { // abstraction or subpatch
} else {
return new SubpatchObject(ptr, parent);
}
} else if (canvas->gl_isgraph) {
return new GraphOnParent(ptr, parent);
} else {
return new SubpatchObject(ptr, parent);
}
}
case hash("array define"):
Expand All @@ -635,8 +657,10 @@ ObjectBase* ObjectBase::createGui(pd::WeakReference ptr, Object* parent)
case hash("pd~"):
return new PdTildeObject(ptr, parent);
case hash("scalar"): {
if (checked->g_pd == scalar_class) {
return new ScalarObject(ptr, parent);
if (auto checked = ptr.get<t_gobj>()) {
if (checked->g_pd == scalar_class) {
return new ScalarObject(ptr, parent);
}
}
break;
}
Expand All @@ -655,15 +679,17 @@ ObjectBase* ObjectBase::createGui(pd::WeakReference ptr, Object* parent)
case hash("knob"):
return new KnobObject(ptr, parent);
case hash("openfile"): {
char* text;
int size;
pd::Interface::getObjectText(checked.cast<t_text>(), &text, &size);
auto objText = String::fromUTF8(text, size);
bool hyperlink = objText.contains("openfile -h");
if (hyperlink) {
return new OpenFileObject(ptr, parent);
} else {
return new TextObject(ptr, parent);
if (auto checked = ptr.get<t_gobj>()) {
char* text;
int size;
pd::Interface::getObjectText(checked.cast<t_text>(), &text, &size);
auto objText = String::fromUTF8(text, size);
bool hyperlink = objText.contains("openfile -h");
if (hyperlink) {
return new OpenFileObject(ptr, parent);
} else {
return new TextObject(ptr, parent);
}
}
}
case hash("noteout"):
Expand All @@ -687,7 +713,6 @@ ObjectBase* ObjectBase::createGui(pd::WeakReference ptr, Object* parent)
}
default:
break;
}
}
}
return new TextObject(ptr, parent);
Expand Down
7 changes: 7 additions & 0 deletions Source/PluginProcessor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,13 @@ void PluginProcessor::flushMessageQueue()
messageDispatcher->dequeueMessages();
}

void PluginProcessor::doubleFlushMessageQueue()
{
setThis();
messageDispatcher->dequeueMessages();
messageDispatcher->dequeueMessages();
}

void PluginProcessor::initialiseFilesystem()
{
auto const& homeDir = ProjectInfo::appDataDir;
Expand Down
1 change: 1 addition & 0 deletions Source/PluginProcessor.h
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ class PluginProcessor final : public AudioProcessor
void updateAllEditorsLNF();

void flushMessageQueue();
void doubleFlushMessageQueue();

#ifndef JucePlugin_PreferredChannelConfigurations
bool isBusesLayoutSupported(BusesLayout const& layouts) const override;
Expand Down

0 comments on commit 29bab25

Please sign in to comment.