Skip to content

Add generic RT/NRT shared resource system (#147)#152

Open
kaoskorobase wants to merge 2 commits into
developfrom
resource-system
Open

Add generic RT/NRT shared resource system (#147)#152
kaoskorobase wants to merge 2 commits into
developfrom
resource-system

Conversation

@kaoskorobase
Copy link
Copy Markdown
Member

Summary

  • Adds Methcla_ResourceDef to the plugin C API (include/methcla/plugin.h) — a struct describing a named resource type with uri, instance_size, options_size, mutability hint, and configure/construct/destroy callbacks. Plugins register definitions via methcla_host_register_resource_def.
  • Adds ResourceId, allocResourceId(), freeResourceId(), resourceNew(), and resourceFree() to the C++ client API (include/methcla/engine.hpp).
  • Adds OSC commands /resource/new <requestId:i> <resourceId:i> <uri:s> and /resource/free <id:i> with notifications /resource/ready <id:i>, /resource/error <id:i> <msg:s>, and /resource/destroyed <id:i>.
  • Construction runs on the NRT worker thread; freePending handles the race where /resource/free arrives while construction is still in progress.
  • Adds AsyncLatch (condvar + timeout) to tests/methcla_tests.hpp as a robust replacement for sleepFor() in async tests.

Deferred

  • Synth-side kMethcla_ResourcePort integration
  • AudioBuffer built-in plugin (perform_with_resources host primitive)

Test plan

  • ResourceTests.UnknownUriReturnsError — unknown URI → /resource/error
  • ResourceTests.KnownUriConstructsAndNotifiesReady — registered URI → /resource/ready
  • ResourceTests.FreeLiveResourceDestroysIt — free after ready → /resource/destroyed
  • ResourceTests.FreeDuringConstructSetsFreePending — free during construction → /resource/destroyed only (no /resource/ready)
  • Full suite (ctest --preset debug): 19/19 passed

Introduces Methcla_ResourceDef plugin API for defining named resource
types, OSC commands /resource/new and /resource/free, and
/resource/ready, /resource/error, /resource/destroyed notifications.
Supports freePending to handle races between in-flight construction and
a concurrent free request. AsyncLatch condvar helper replaces
sleepFor() in tests.
Comment thread include/methcla/engine.hpp Outdated
}
};

inline static std::ostream& operator<<(std::ostream& out,
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider implementing this in the Id base class, similar to how it's done in StrongId. Same for NodeId.

Comment thread include/methcla/engine.hpp Outdated
void resourceNew(ResourceId id, const char* uri)
{
beginMessage();

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider getting rid of this newline (same in similar Request methods).

Comment thread include/methcla/engine.hpp Outdated

oscPacket()
.openMessage("/resource/new", 3)
.int32(0) // requestId (unused)
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

requestId is not needed, remove it from the OSC protocol.

Comment thread include/methcla/engine.hpp Outdated
return m_audioBusIds;
}

ResourceId allocResourceId()
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Follow the existing pattern (see nodeIdAllocator).

Comment thread include/methcla/engine.hpp Outdated
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Follow the existing pattern (see nodeIdAllocator).

Comment thread include/methcla/plugin.h Outdated
const char* message);

//* Register a resource type definition.
void (*register_resource_def)(Methcla_Host* host,
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Move this between register_synthdef and register_soundfile_api. Binary compatibility is not a concern at this point.

Comment thread include/methcla/plugin.h
Comment thread include/methcla/plugin.h
Comment thread src/Methcla/Audio/Engine.cpp
Comment thread include/methcla/plugin.h Outdated

static inline void
methcla_host_register_synthdef(Methcla_Host* host,
const Methcla_SynthDef* synthDef)
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rename to def for consistency.

Comment thread src/Methcla/Audio/Engine.cpp Outdated
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rename to def for consistency.

Comment thread src/Methcla/Audio/Engine.hpp Outdated
@@ -138,6 +139,9 @@ namespace Methcla { namespace Audio {
//* Register SynthDef.
void registerSynthDef(const Methcla_SynthDef* synthDef);
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rename to def for consistency.

Comment thread src/Methcla/Audio/EngineImpl.cpp Outdated
const int32_t resourceId = args.int32();
if (resourceId < 0 ||
static_cast<size_t>(resourceId) >= m_resources.size())
return;
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Avoid early returns.

Comment thread src/Methcla/Audio/EngineImpl.cpp Outdated
{
entry.freePending = true;
}
else if (entry.state == ResourceEntry::State::Live &&
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think there's a state transition missing here, according to #147:

refcount>0  → free_pending=true,
     └──────────────────────────────────────  Free                 │                  drain via release path

Comment thread src/Methcla/Audio/EngineImpl.cpp Outdated
}

namespace {
struct ResourceDestroyCommand
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Prefer a class here, with members private where possible. Prefix members properly.

Comment thread src/Methcla/Audio/EngineImpl.cpp Outdated
}
else if (msg == "/resource/new")
{
class ResourceConstructCommand
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's more readable in the long run to avoid inline class definitions. Pull this and others out in an anonymous namespace.

Comment thread src/Methcla/Audio/EngineImpl.cpp Outdated
void EnvironmentImpl::scheduleResourceDestroy(int32_t resourceId)
{
auto& entry = m_resources[resourceId];
sendToWorker(
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use the template version.

Comment thread src/Methcla/Audio/EngineImpl.cpp Outdated
entry.def = nullptr;
entry.data = nullptr;
self->impl
->sendToWorker<EnvironmentImpl::ResourceDestroyedNotification>(
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think an extra memory allocation can be avoided if this class also functions as the notification.

Comment thread src/Methcla/Audio/EngineImpl.hpp Outdated
std::list<const Methcla_SoundFileAPI*> m_soundFileAPIs;

using ResourceDefMap =
std::unordered_map<std::string, const Methcla_ResourceDef*>;
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can't use std::string here because it allocates. See definition of SynthDefMap.

Comment thread src/Methcla/Audio/EngineImpl.cpp Outdated
{
auto* self = static_cast<ResourceDestroyCommand*>(d);
auto& entry = self->impl->m_resources[self->resourceId];
entry.state = EnvironmentImpl::ResourceEntry::State::Free;
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Better to fully initialize by assigning a default constructed entry.

Comment thread src/Methcla/Audio/EngineImpl.cpp Outdated
const Methcla_ResourceDef* def = it->second;
auto& entry = m_resources[resourceId];
entry.state = ResourceEntry::State::Constructing;
entry.freePending = false;
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Better to fully initialize by assigning a default constructed entry.

Comment thread src/Methcla/Audio/EngineImpl.hpp Outdated
}
}

class ResourceErrorNotification : public Notification
Copy link
Copy Markdown
Member Author

@kaoskorobase kaoskorobase Jun 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This class is probably not needed in the header and can be moved to the source (encapsulation).

Comment thread src/Methcla/Audio/EngineImpl.hpp Outdated
sendToWorker<ResourceReadyNotification>(resourceId);
}

void registerResourceDef(const Methcla_ResourceDef* def);
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add context comment (NRT)

Comment thread src/Methcla/Audio/EngineImpl.hpp Outdated
// notification.
void scheduleResourceDestroy(int32_t resourceId);

class ResourceDestroyedNotification : public Notification
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could be merged with the destroy command to avoid an allocation (see earlier comment). In any case, this is probably not needed in the header and can be moved to the source (encapsulation).

Comment thread src/Methcla/API.cpp
size_t realtime_memory_size = 1024 * 1024;
size_t max_num_nodes = 1024;
size_t max_num_audio_buses = 1024;
size_t max_num_resources = 256;
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These constants are duplicated in various places. I'm wondering whether there's a clean way to avoid the duplication.

Comment thread tests/resource_tests.cpp Outdated
}

// ---------------------------------------------------------------------------
// Slice 1: Unknown URI → /resource/error
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove "Slice" comments. Add comments to explain tests where necessary.

Comment thread tests/resource_tests.cpp Outdated
return false;
});

// Send /resource/new and /resource/free back-to-back without waiting.
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could be sent in a single bundle. This guarantees they're executed in the same process callback.

- Add kMethcla_UnsupportedResourceTypeError; replace notifyResourceError
  with throwErrorWith so unknown-URI errors flow through the standard
  catch/log path instead of a bespoke /resource/error notification
- Report out-of-range resource id in /resource/free as ArgumentError
- Move ResourceReadyNotification, NodeTreeStatisticsCommand, and
  RTMemoryStatisticsCommand out of method bodies into the anonymous
  namespace; rename Command* → *Command for consistent naming
- Consolidate operator bool() and operator<< into the IdBase template
- Add resourceIdAllocator() to the Engine interface; rename audioBusId()
  → audioBusIdAllocator() for consistency
- Fix Methcla_Host initializer order to match struct field order after
  register_resource_def was repositioned
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.

1 participant