The title really explains it all, heres my server code:
function DataService:KnitStart()
print("Module Loaded!")
for _, quickJoiner in Players:GetPlayers() do
playerAdded(quickJoiner)
end
Players.PlayerAdded:Connect(playerAdded)
Players.PlayerRemoving:Connect(playerRemoved)
end
Here’s My Client Code
-- Services
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Knit = require(ReplicatedStorage.Packages.Knit)
-- Install Knit
Knit.AddControllers(ReplicatedStorage.KnitControllers)
Knit.Start()
Knit.OnStart():andThen(function()
print("OnStart Fired!")
local DataService = Knit.GetService("DataService")
DataService:Get("coins"):andThen(function(coins)
print(coins)
end):catch(warn)
DataService.Changed:Connect(function(key, value)
print("Changed "..key.." to ".. value)
end)
end):catch(warn):await()
It kind of works-- “Module Loaded” (Server) prints first, which is good! But the “playerAdded” (Server) function inside doesn’t finish running until AFTER “OnStart Fired!” (Client) runs.
Is there a way I can have Knit yeild until the KnitStart function is completely done running?
You’re supposed to do initialisation work in KnitInit for this exact reason. Start is intended to be asynchronous across all services/controllers. You can’t, and you’re not supposed to, wait until a KnitStart finishes running for any service. If you have timing issues with a service or controller’s start lifecycle method then you need to start putting more things into the initialisation phase.
KnitInit is how you “wait” for services to load. By design, services/controllers should be set up and ready to be used by the time KnitStart is getting called. If this is not true, you have a bit of refactoring to do.
I tried :KnitInit(), but it didn’t work either since datastores take a little long to load.
I guess this is what I’ll have to do. But, what do you mean by “refactoring”, is there a way to make Knit.OnStart() yield until a function, completely finishes running, like a promise sort-of-thing?
No, because again, Start is not designed for synchronous operation. OnStart itself just returns a Promise that determines when Knit has finished calling KnitInit on all services and then KnitStart with no issues: KnitServer.luau#L480. This is largely an architecturing problem on your end.
Looking back on this several hours later though, I’ve realised something - I have zero clue what actual problem exists here. Your code is behaving as intended, so I’m not sure why you need to care about the order of prints here. I assume your KnitStart has yielding calls being performed in it which is fine as it runs on a separate thread. The order of events is expected here.
To me it sounds like your real problem isn’t to do with the data lifecycle but instead that you’re expecting data to be immediately available when you call your service’s Get method - this I guess also based on you saying “DataStores take a little long to load” (they don’t) in your reply. You’re trying to get some player data, it isn’t coming through, so you believe it has to do with your service’s lifecycle when that’s actually just not the case, rather that you haven’t designed your getter well.
The trick with data is to not expect anything to be immediately available. Whatever code is interacting with DataStores should be caching player data in an internal table to the service. From there, your getter method should determine a course of action: if the player data does in fact exist, simply access that table, otherwise wait for the DataStore code to finish for the player.
I have a lightweight data tooling library that I use in my active experience to work with data. I’ll share with you the exact code that I use for getting player data (I don’t expect you to copy it but rather learn the flow of events that is happening here):
Some code wants a player’s save data. First it checks if the player’s data has been loaded (attributes are a lazy but easy way to get it done and it interops with the rest of my codebase incredibly well without having to settle it in pure Luau); if it has, then it will return the profile. You can ignore the IsClient bits, that’s special handling for replicating data to clients.
As for what script sets DataLoaded, I do it in a separate script because I have a lot of pipelines running for player joins. You don’t have to but it’s roughly equal.
The issue was that :Get() was being called before the server had a chance to cache the data. As a result, it tried to index a key in a table that didn’t exist.
I really like the idea of using attributes—I’ll probably implement that instead of my temporary solution. Thanks!