Force single module instance

If I wanted to limit a particular module within a plugin to a single instance (i.e. you can create one and only one) how might I go about that?

what do you need from it? you can allocate a singleton in the global space that is recognised by your modules and reuse data you store into. Beware you are polluting application space, so you have to be careful about allocations

This is possible and is done in VCV Prototypeā€™s Python backend since Python isnā€™t designed to be embedded. Just make your module do nothing unless it ā€œownsā€ the singleton. There may be better ways to abstract the following code in your case, but hereā€™s the simplest form.

static bool owned = false;

struct MyModule {
	bool running = false;

	MyModule() {
		if (!owned) {
			// Take ownership of singleton
			owned = true;
			running = true;
		}
	}

	~MyModule() {
		if (running) {
			// Release ownership of singleton
			owned = false;
		}
	}

	void process(...) {
		if (!running)
			return;

		...
	}
}

or maybe

static MyModule* singletonModule = NULL;

and release only if this == singletonModule.

1 Like

I donā€™t want to ā€˜make it do nothingā€™, I want to suppress creation of any more than one instance of the module. The module is for setting plugin themes, it makes no sense to allow more than one instance.

Could I, upon detecting creation of a second instance, call the code that the right click menu delete option calls in order to enforce this, or is there a better method?

Even if itā€™s possible, Iā€™m not sure this would be a good approach in term of UX; trying to add a module and nothing happens would definitely feel like a bug to the user.

Maybe just rely on a singleton approach as suggested and make any instance of the module basically act as ā€œclonesā€ ?

2 Likes

Compromise:

image

1 Like

Out of curiosity why would you want the second one blank as opposed to identical and synchronized?

I donā€™t know if this will work but you could maybe overload the new operator for your module class and if you have a singleton already instantiated donā€™t allocate memory in this case and just return a nullptr. The Rack engine (maybe) checks for a nullptr if it creates a module instance and then wonā€™t create/add a new module in this case?

Why might you want the second one identical and synchronised (along with the extra overhead of a functioning as opposed to effectively disabled module) as opposed to blank? It just sets themes, so you only need one instance and the blank is a gentle nudge to the user to that effect.

I agree with @23volts, selecting a module and having nothing happen would look like a bug, so am happy with the disabled module message solution.

Donā€™t attempt anything like this because it will decrease the stability of Rack with any hack you invent. The user should be able to create as many modules as they want, but you can limit the functionality of additional instances.

Absolutely donā€™t override new or anything like that. Instant crash when anything in Rackā€™s implementation changes, which would crash the userā€™s DAW in Rack for DAWs.

1 Like

Thatā€™s exactly the solution Iā€™ve arrived at so that should be ok yes? Not overriding anything, just disabling functionality of additional instances and notifying the user accordingly.

Please forgive me for piling on in this thread, but I think I have a similar issue with my Meander module that I need to better understand. I can launch two instances of Meander, but it is obvious that the two instances are interacting and stepping on each otherā€™s data. I suspect that this is due to the 1700 lines of global scoped variables and functions. Do all instances of the module instances share the global memory? If so, where should I put this massive amount of data and logic that makes Meander work, such that each instance has itā€™s own copy to work with? Should it go in the Module scope?

I initially thought that Meander would only allow one instance but as mentioned here, that is difficult to achieve. I can imagine that someone might want multiple instances of Meander if I can figure out how to allow that. If I canā€™t, I suppose I will have to go the reduced functionality route as discussed above.

Thanks for letting me butt in:)

BTW, the massive amounts of global data and functions are a result of me moving C code from my Meander for Windows application to my module.

Yes, absolutely. If your library uses globals, you cannot use multiple instances. In your pluginā€™s case, users probably donā€™t need multiple instances, so itā€™s probably not worth it to go through your code for several hours and contextifying everything into objects. However you should add protection against users adding multiple instances, by disabling your DSP (as in my example above). Additionally, you wonā€™t be able to use multiple instances of your plugin across multiple Rack for DAWs instances in the same DAW.

The following is bad and will break with multiple instances.

float global;

float MyLibrary_process(float in) {
	global = in;
	global *= 2;
	return global;
}

To handle multiple instances, your library needs to look like this (if using C).

struct MyLibraryContext {
	float local;
};

float MyLibrary_process(MyLibraryContext* ctx, float in) {
	ctx->local = in;
	ctx->local *= 2;
	return ctx->local;
}

or with everything in C++ classes if using C++.

@contemporaryinsanity I am trying to do this same thing with Meander. Iā€™m using the method that @Vortico recommended and I can disable the DSP processing, but my ModuleWidget is still doing some UI draws on the 2nd instance. How are you doing the DISABLED display? Are you doing anything with your ModuleWidget?

Iā€™m doing exactly as @Vortico suggested.

At global module scope:

static bool owned = false;

The top of my module constructor:

	bool running = false;

	RSGroundControl() {
		if(!owned) {
			owned = true;
			running = true;
		}

My module destructor:

	~RSGroundControl() {
		if(running) {
			owned = false;
		}
	}

The top of my widget constructor:

struct RSGroundControlWidget : ModuleWidget {
	RSGroundControl* module;

	RSGroundControlWidget(RSGroundControl *module) {
		setModule(module);
		this->module = module;

		box.size.x = mm2px(5.08 * 10);
		int middle = box.size.x / 2 + 1;
		int quarter = middle / 2;

		if(module)
			if(!module->running) {
				addChild(new RSLabelCentered(middle, box.size.y / 2, "DISABLED", 16));	
				addChild(new RSLabelCentered(middle, box.size.y / 2 + 12, "ONLY ONE INSTANCE OF GC REQUIRED"));
				return;
			}
			// Draw everything else here

The top of my widget step():

	void step() override {
		if(!module) return;
		if(!module->running) return;

Hope this helps.

Thanks, this is all working for me correctly now that I am doing it exactly how you are doing it. In the process I fixed a couple of long standing issues that should make my module more stable and reliable now.

1 Like

Out of curiosity: if you drag out two modules with this method, and the second is inactive, and then you delete the first, the second remains inactive and you canā€™t do anything unless you drag out another one, right?

Perhaps if your bool was a std::atomic you could also check if owned = false in your ::process to detect that case? So in ::step do the compare_exchanged_weak thingy and then owned = true running = true with the appropriate atomic operations.

Interesting thread.

Right. There will be a facility for modules to individually override the globally selected theme, so once themes are set via Ground Control, it can be removed from the patch entirely if one wishes, theme choosing functionality will still be available on a module by module basis, just not theme setting.

I could, will consider that once everything else is working, thanks for the suggestion.

Iā€™m sure you realize ther is absolutely no reason to put a bunch of data at global scope?