Help with StreamingEnabled?

All right, so I’ve left considering whether StreamingEnabled is right for a project I’m working on on the back burner for a bit, but I’ve come to a point where I need to make a decision, and that decision may hinge on a solution to this problem. I have a scenario—I feel like it’d be a pretty common one nowadays.

Scenario:
Suppose there’s a part over off yonder that I want to customize locally. The player isn’t guaranteed to be within streaming distance of this part at any given moment. So, I attempt to customize the part upon the player joining the game, with a :WaitForChild("",timeout) attached. Suppose it times out. Then, I’ve no choice but to connect an event on the client in case that part shows up; however, I’ve also got to connect an event on the server in case that part, or its ancestors, get deleted, so I can disconnect the client connection and avoid a memory leak…
Except what if the part never existed to begin with? Well, we have to make that server connection somehow, and the client is the only one that knows it’s missing something, so a RemoteFunction it is. If the server finds the part, good, make the deletion connections, otherwise, return to the client and have it disconnect its own constant check. Great! But…
We need to connect and disconnect the server connection(s) at the behest of the client… Right. Sounds dangerous. Sounds scary. Sounds like my game will not be consistently playable when the vulnerability is found, and all the memory is eaten up.

Am I missing something? Am I overlooking something? Am I wrong? What’s going on?

i’m not completely sure what you’re talking about and only knew about streaming enabled when i googled it today but in a minor note i’ve seen this custom function in the default “Animate” script inside the player characters which will first get the var of the parent and the child name, it starts with trying to find the first child then if it can’t it will not return anything yet and continue on and keep checking if a child named the same thing has finally added and will return that child

it basically is a more guaranteed alternate of waitforchild and will never error unless there is some fatal issues in the player’s assets loading

im not good at explaining but this is the exact script

function waitForChild(parent, childName)
	local child = parent:findFirstChild(childName)
	if child then return child end
	while true do
		child = parent.ChildAdded:wait()
		if child.Name==childName then return child end
	end
end

i think this can be useful if you need to confirm an extremely important and major part load in a game so if this is not the right thing you need you can ask me

There’s no difference between that version of WaitForChild and the one inherited from Instance besides the way it’s implemented. I don’t recommend using this and pointed it out in another thread regarding using FindFirstChild and WaitForChild; the former is not a substitute for the latter.

Regarding the OP, parts never get streamed out on the server. Is there a reason why the server is getting involved here for a part that you want to customise locally? If your main concern is locally customising a part but you also want to use StreamingEnabled, then your main issue is accounting for a part that may potentially be streamed out or not exist.

When using StreamingEnabled you should always be conscious of this behaviour where a part may not exist and use a pattern that’ll allow you to handle a new part as it gets streamed in. The recommendation on the Developer Hub is CollectionService; other developers may suggest abstractions like Binder to help you out.

For me personally, in the case of local customisation I would never use WaitForChild whether it’s a single part or a group of parts. I will always use the pattern that goes in the form “handle the ones that exist as well as ones that may exist in the future”. As an applicable example, developers should be using this pattern to handle anything to do with PlayerAdded:

local Players = game:GetService("Players")

local function playerAdded(player)
    doSomethingToPlayer(player)
end

Players.PlayerAdded:Connect(playerAdded) -- Runs for new players
for _, player in ipairs(Players:GetPlayers()) do -- Runs for existing players
    playerAdded(player)
end

So assuming this part you want to customise is created by the server (either precreated in Studio or created by the server at runtime) but the customisation bit is local, then you should be applying the above pattern to this scenario as well. Customise whatever parts already exist that need to be customised and then apply the customisation for parts that may be streamed in.

Your proposed solution in the first paragraph, potentially sans the WaitForChild, is good enough except for the part where you brought up the server. The server doesn’t need to be involved here at all. Even if your concern here lies with managing connections the server still doesn’t need to be involved; manage it from the client. You can cache connections on the part and when it gets removed you can remove those connections. A good pattern developers might recommend for this is Janitor but you can just as easily handle it without a connection management abstraction.

Going into the second paragraph, if you need the server to make a connection you can do that safely without worrying about whether or not the part will exist because the server will always see the full Workspace. The only instance where that’d matter is if the part is predictably (or unpredictably) deparented from the Workspace which is just a standard programming problem for you to resolve irrespective of if StreamingEnabled is on or not.

This help, do I not understand, or do you not understand? Feel free to clarify or ask questions.

Erm, okay. I guess I’ll dissect what I said, first.

Suppose there’s a part over off yonder that I want to customize locally . The player isn’t guaranteed to be within streaming distance of this part at any given moment.

This means the part may or may not be streamed in to the client.

So, I attempt to customize the part upon the player joining the game, with a :WaitForChild("",timeout) attached.

I make a guess here that the part does exist (is streamed in). I may be wrong, so I add a timeout so the client doesn’t hang.

Suppose it times out.

The part is not streamed in. This could be because the client is too far, or because it truly doesn’t exist.

Then, I’ve no choice but to connect an event on the client in case that part shows up; however, I’ve also got to connect an event on the server in case that part, or its ancestors, get deleted, so I can disconnect the client connection and avoid a memory leak…

I still want to make the customization. So, I wait on the client until I see the part; but I want to be efficient—I don’t want the client to be waiting forever in the case that it doesn’t exist. If I were to skip this, there may be bloating. I may be waiting for more and more parts over time that don’t exist.

Except what if the part never existed to begin with? Well, we have to make that server connection somehow, and the client is the only one that knows it’s missing something, so a RemoteFunction it is. If the server finds the part, good, make the deletion connections, otherwise, return to the client and have it disconnect its own constant check.

Only the server knows if the part is really there. I want it to check and report back. The server should also track whether the part is deleted while the client has yet to stream it in (hence the server’s own connection).

The problem I posed is that I fear spamming, since there’s no way for me to verify that the client is lying.

Anyway, your response seemed to be a catch-all maybe borne out of some confusion, but there were some points that got me thinking differently.

So, I suppose the client can begin by invoking the server, asking if the parts it wants to customize do exist. The server returns what it finds, and the client attempts to customize them, yielding along the way in case they aren’t streamed in. Then, there’s still the issue of waiting for parts that (now) may or may not exist. How would you suggest solving this?

Also where were you going with CollectionService?

Thanks for the clarification. Your explanation was a bit confusing and yes that’s partially why I approached this with a catch-all. The main reason for that response seems to be that you’re worrying about something you don’t need to be and maybe a change in approach or understanding here might help you get down a solution.

There is one bit of additional information you gave that wasn’t present in the OP:

Assuming I’m interpreting this correctly, what you’re saying here is the server may potentially remove something that hasn’t yet been streamed to the client and so that’s where this whole spiel about the server’s involvement and connection management is coming into play, is it?

What kind of connections are you making such that a part being streamed out or removed would cause a memory leak? You’re just handling the customisation of a part, aren’t you? Mostly the only kind of memory leak you can and should be facing on local customisation is having a strong reference to a part unless you’re doing something else (i.e. GetPropertyChangedSignal/Changed).

As for your concern of the client waiting forever, this is why in my first post I said that I would personally never use WaitForChild. Use an event-based system so that you can have your code act as in when it needs to rather than waiting an arbitrary amount of time. This is where CollectionService, the aforementioned pattern and the relevant abstractions/alternatives I linked come into play: they allow you to remove waiting from your workflow and instead be more reliant on events.

Here’s the thing I still don’t understand: where are you going with involving the server and why? If the server is not deparenting anything then the part will always exist to the server, StreamingEnabled is only relevant to client rendering. If the server is deparenting things, the client still doesn’t need to check with the server if the thing exists because it’s local customisation. When I said the server didn’t need to be involved, I really meant “stop thinking about how to use the server here”.

If you’re asking how I would solve this, that’s where I was going with CollectionService because it allows me to create an event-driven system. Any parts I want to make customisable would be marked with a given tag and then I’d have the client listen to new instances that are given that tag. Parts streamed in that have that tag fire added signals so I can just apply my customisation right away.

Client doesn’t need to yield for anything, in fact I would consider any sort of yielding for local customisation an anti-pattern. Unless you have a specific reason, you don’t need to rely on the existence of the part to apply your customisation. So really it comes down to how you think of the problem: it should not be based on something’s existence, but rather how you handle a new existence. Don’t wait for things: act on things when you receive them.

Assuming I’m interpreting this correctly, what you’re saying here is the server may potentially remove something that hasn’t yet been streamed to the client and so that’s where this whole spiel about the server’s involvement and connection management is coming into play, is it?

Yes.

What kind of connections are you making such that a part being streamed out or removed would cause a memory leak?

Could be any. My example strongly implies a RunService connection, but there might as well be others that would be useful to attach locally. When I say “customization,” that could be extended to changing any property of the part, including CFrame.

Mostly the only kind of memory leak you can and should be facing on local customisation is having a strong reference to a part unless you’re doing something else (i.e. GetPropertyChangedSignal/Changed).

I like keeping track of my connections, so they would probably be referenced somewhere. These references are the memory leak risk. They offer me tighter control over performance in general, but obviously require diligent maintenance and care needs to be taken to avoid duplication.

If the server is deparenting things, the client still doesn’t need to check with the server if the thing exists because it’s local customisation.

Following my logic, I’d need to verify if the part exists at least periodically to keep my connections on the client-side tight. Like I’ve said, I don’t want the client to wait on parts that don’t exist—the way I’m thinking about the problem, I risk a clog otherwise. Maybe you’re confused because you aren’t thinking about the problem like I am, and to your solution, this is an unnecessary step. To my solution, it isn’t. But, in my original post, I stated that I could be wrong and that I was accepting of different, more efficient approaches.

Any parts I want to make customisable would be marked with a given tag and then I’d have the client listen to new instances that are given that tag. Parts streamed in that have that tag fire added signals so I can just apply my customisation right away.

This makes sense. I’ll think through it some more.

Thanks for clarifying again. It’s always good to find out new information from continued responses because some of the things you think are implied are not real implications here. I would never have guessed a need for constant application with something like RunService and just assumed you were looking for simple customisation like a few property changes.

To hone in on RunService, it’s at virtually no cost for you to leave a connection running in the background. I get the concern for memory leaks and wanting to manage your connections predictably but to me it sounds more troublesome than its worth writing code for. That and it makes way for a good pattern where you can batch hook updates to be made by one connection rather than several.

When it comes to any other type of connection on the part or a connection that holds a strong reference to the part that’s a different story. I think overall it would be best to look into the CollectionService pattern(s) I mentioned in other posts, it seems like a strong fit for your use case.

The server’s main concern then is tagging customisable parts with a given tag or you can do that via the precreated part in Studio. The client then tracks new instances on that tag and sets up connections. If the server deletes the part or the part is streamed out, it gives you a chance to clean up that connection. If the server deletes the part before the part is streamed in, you have no connections to begin with that’d need to be cleaned up.

For anything that needs a frequent update, I’d prefer to hook into a single RunService connection rather than multiple. I do this for other game systems in my main project: there’s one main script that sets up a Heartbeat connection and allows other scripts to register things to happen during that Heartbeat.

CollectionService works well for me as far as I can tell. Much appreciated.