Instatiating a new class (ModuleScript OOP) within a for loop results in odd behavior

Here I have a BasicSound module that is made to instantiate new sounds using the built-in Sound class. However, when I pool said sounds via calling the .new(...) method in my module within a for loop and inserting it into a table, it refuses to pool and create new instances of BasicSound and ALSO the Sound instance to go along with it. In turn, this’d lead to that BasicSound pool playing only that one Sound instance that is referenced rather than their own separate / newly instantiated sounds.

I made sure to check my code—I did forget to add a missing :Clone() function when creating a new Sound instance. Though that didn’t seem to fix it at all. Then I double-checked the module I was using to create classes, which is this here, though that didn’t seem to be the one causing the issue either as I tried another method of OOP where it does not involve this module whatsoever.

Code snippet below (NOTE: Not the actual full script but I hope it gets the point across):

-- BasicSound.luau

local BasicComponent = require(ReplicatedStorage.Modules.BasicComponent)

local BasicSound, super = class("Sound", BasicComponent)

local function __makeSound(name: string, category: string, path: string, volume: number, isLooped: boolean, speed: number): Sound
	local snd: Sound = ASSETS_FOLDER:FindFirstChild(path):FindFirstChild(name):Clone()
	...
	snd.Parent = SoundService:FindFirstChild(category)
	return snd
end

function BasicSound:__init(name: string?, settings: SoundSettings?, path: string?)
	settings = settings or {}
	super.__init(self, __makeSound(name, settings.Category, path, settings.Volume or 1, settings.Looped or false, settings.PlaybackSpeed or 1))
	...
end
-- LocalScript.luau

local ReplicatedStorage = game:GetService("ReplicatedStorage")

local BasicSound = require(ReplicatedStorage.Modules.Game.BasicSound)
local Button = require(ReplicatedStorage.Modules.UI.Button)
local Pooler = require(ReplicatedStorage.Modules.Game.Components.Pooler)

local newButton = Button.new() -- Custom button class/module

-- Simply does a for loop, inserts objects into a table (32 times in this case), and those objects can be reused/modified later as per how object pooling is intended to work
local soundPool = Pooler.new(32, BasicSound.new("Placeholder", {}))

newButton:AddCallback(Button.ACTION_CONTEXT.ButtonClick, function()
	soundPool:IterateOnce(function(sound)
		sound:Play()
	end)
end)

newButton:Register()

Output:

image

First thing I’m seeing:

	local snd: Sound = ASSETS_FOLDER:FindFirstChild():FindFirstChild(name):Clone()

You have a FindFirstChild() with no arguments. I’ve never done this but I assume it may give you an error or unpredictable behaviour.

Second, it’s a little confusing, but it seems to me you’re instantiating a pool of 32 entries with each defaulting to BasicPool with name = Placeholder. However, looking at your code, wouildn’t this deterministically give you the same sound every time?

Also, share your code for your loop. BasicPool.new() will give you a table, and if you want 32 instances of this table you’ll have to clone it, essentially. Even then, they’ll all point to a unique Sound instance with the same properties

1 Like

Oops- That :FindFirstChild() call was meant to have the path pointing to where the sound is located—my bad! But in the code, it is filled with the argument so I must’ve forgotten to put it in😅

I’ll take a better look later, here’s what I have coded for creating an object pool:

-- Pooler.luau

local ReplicatedStorage = game:GetService("ReplicatedStorage")

local Constants = require(ReplicatedStorage.Constants)

local class = require(ReplicatedStorage.Modules.Lib.class)

local Pooler = class("Pooler")

function Pooler:__init(amount: number, object: any)
	
	if (amount >= Constants.MAX_OBJECT_POOL_AMOUNT) then
		
		amount = Constants.MAX_OBJECT_POOL_AMOUNT
		
	end
	
	self.__objects = {}
	self.__iteration = 0
	
	for _ = 1, amount, 1 do
		
		table.insert(self.__objects, object)
		
	end
	
end

function Pooler:IterateOnce(func: (... any) -> ())
	
	if (self.__iteration >= #self.__objects) then
		
		self.__iteration = 0
		
	end
	
	self.__iteration += 1
	
	func(self.__objects[self.__iteration])
	
end

function Pooler:ForEach(func: (... any) -> ())
	
	self.__iteration = 0
	
	for _, object in pairs(self.__objects) do
		
		func(object)
		
	end
	
end

Using a regular for loop to cache a BasicSound actually worked as the intended behavior!

local MAX_ITERATIONS = 32;

local cache: typeof(BasicSound) = {};

for _ = 1, MAX_ITERATIONS, 1 do
	
	local snd: typeof(BasicSound) = BasicSound.new("Placeholder", {}, BasicSound.CHANNELS.Sounds);
	
	table.insert(cache, snd);
	
end

local index = 0;

newButton:AddCallback(Button.ACTION_CONTEXT.ButtonClick, function()
	
	if (index >= MAX_ITERATIONS) then
		
		index = 0;
		
	end
	
	index += 1;
	
	cache[index]:Play();
	
end);

newButton:Register();

… But again, it isn’t what I want since I want to make use of my Pooler module to reuse it.

Its because in your previous code, your pool had 32 entries, with each entry being the same table. You essentially did BasicSound.new() which made one table, passed that into a function, and inserted the same table 32 times. Those 32 tables are the same table, its what we call a reference to a table (sorry if you know this i dont wanna yap). But basically reading data from any of those 32 entries is just gonna be reading data fron rhe same BasicSound, and so is writing a key value pair to any of those tables. Your new for loop explicity calls new() on each iteration, creating a unique table. If you wanna use your Pool, you could try table.clone on the default BasicSound passed in, or pass in a function that returns a new BasicSound, and call that function in your loop

1 Like

I shouldve also mentioned table.clone may not be enough because it doesnt properly clone sub tables. Could try looking into deep cloning, but passing in a function may be easiest

1 Like

I see. (And yes I do understand that the reference to table may be the problem.) I’ll try and rewrite my pooling module later but for now I might just stick with just caching BasicSound with a table. Thank you for your insight!

1 Like

[…] or pass in a function that returns a new BasicSound, and call that function in your loop

This actually worked too! Thank you again!

local soundPool = Pooler.new(32, function(): typeof(BasicSound)
	
	return BasicSound.new("Placeholder", {}, BasicSound.CHANNELS.Sounds)
	
end)
1 Like

This runs the function 32 times that returns a new BasicSound and inserts it into the Pooler’s table. So that checks it! ^^

1 Like

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.