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()
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
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();
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
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
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!