Client Side doesn't receive the same data the server has

So my problem is that I have two modules for client & server. When the server updates it’s values, it sends a remote for the client side to replicate those values. However, the values on the client side don’t match up with the serverside values.

The "TotalAttachParts" value in the client doesn't replicate to the values that the server currently has, but instead replicates the previous values it had before the value changed.

This is really my only problem, and I can't figure out why.

For more context, basically the attachparts of a block are supposed to appear when u place it down. Although the attachparts are seen to be invisible, unless you place another block, then the previous attachparts will appear.

I’ll have a video at the very bottom to show you what my bug looks like.

If you’re wondering what the attachparts are, it’s basically just the orange dots on the blocks I’m placing down in the video

Server

function BuildServiceServer.PlaceBlock(self: BuildServiceServer, player: Player, blockName, placementInfo)
-- just gets the values needed
	local char = player.Character or player.CharacterAdded:Wait()
	local player_Folder = char.Parent
	local blockFolder = player_Folder:FindFirstChild("BlockFolder")
	local block = blocks:FindFirstChild(blockName)
	if not block then return end
	block = block:Clone()
	if not self:placeable(block, placementInfo) then return end
	local inputPart: Part = block:FindFirstChild("InputPart", true)
	
	--print(placementInfo)
	
-- checks if mouse is hovering over a part that can be interacted with
	if CollectionService:HasTag(placementInfo.MouseObject, "AttachPart") then
		CollectionService:AddTag(placementInfo.MouseObject, "AttachPartTakened")
		local id = HttpService:GenerateGUID(false)
		
		block:SetAttribute("AttachmentId", id)
		placementInfo.MouseObject:SetAttribute("AttachmentId", id)
		self.AttachPartsTakened[id] = placementInfo.MouseObject
	end
	
	[[-- Just some extra stuff that isn't important to the main problem

block.PrimaryPart = inputPart or block.PrimaryPart
	block:PivotTo(placementInfo.CFrame)
	block.Parent = blockFolder
	block:SetAttribute("OwnerId", player.UserId)
	table.insert(self.Objects, block)
	self.TotalAttachParts = self.TotalAttachParts
	
	Debris:AddItem(inputPart, 0)
]]
	
-- These two functions are relatively important for the data, so They're provided under this function
        self:AddAttachparts(block)
	self:SetValue(player) 
end

[[--
 This function updates the table, "TotalAttachParts". I don't understand why it 
aint working since there shouldn't be any bugs with this function.
]]
function BuildServiceServer.AddAttachparts(self: BuildServiceServer, block)
	for _, object in block:GetDescendants() do
		if CollectionService:HasTag(object, "AttachPart") then
			if not table.find(self.TotalAttachParts, object) then
				table.insert(self.TotalAttachParts, object)
			end
		end
	end
end

-- This function is what replicates the tables to the client
function BuildServiceServer.SetValue(self:BuildServiceServer, player)
	local returnInfo = {
Blocks = self.Objects, 
AttachPartsTakened = self.AttachPartsTakened, 
TotalAttachParts = self.TotalAttachParts
}
	
	BuildRequest:FireClient(player, returnInfo)
end

Client

function BuildServiceClient.clientReceiverUpdater(self: BuildServiceClient,returnInfo: {})
	self.Blocks = returnInfo.Blocks
	self.TotalAttachParts = returnInfo.TotalAttachParts
	self.AttachPartsTakened = returnInfo.AttachPartsTakened
	--print(#returnInfo.TotalAttachParts, "Client")
	
	--print("Blocks = ".. #BuildServiceClient.Blocks, "AttachedParts =", BuildServiceClient.AttachPartsTakened, "TotalAttachedParts = ".. #BuildServiceClient.TotalAttachParts)
	
	self:updateAttachments()

end

Video

1 Like

It’s likely block in the block:Clone() is not replicated yet when the event fires, so the server is sending nil over to the client.

You’ll have to work around this somehow. I’m guessing the easiest way would be setting the block’s name (or giving it an attribute) to something unique and then using ChildAdded or DescendentAdded to receive when it replicates over. You’d probably want to send over the number of elements or the list of their names or whatever ID system you use instead, then.

I don’t think that’s the case. I looked through the tables when the client receives it and it prints basically the previous values.

For example, this is what the server and client prints when interacting with one another

-- Output
Server:  ["TotalAttachParts"] =  ▼  {
                       [1] = AttachPart
                    }

Client :  ["TotalAttachParts"] =  ▼  { }

-- This is what it prints when the player places another block

Server: ["TotalAttachParts"] =  ▼  {
                       [1] = AttachPart,
                       [2] = AttachPart
                    }

Client: ["TotalAttachParts"] =  ▼  {
                       [1] = AttachPart,
                    }

Well, that’s what I’m getting at. The client is getting the remote before the server replicates the AttachParts to the client. The parts are received as nil on the client because they don’t exist yet. You’re getting the old values because those are replicated by the time you send the new update.

As a test, try delaying the remote by 1 second, just to see if it replicates properly. If it doesn’t, then I’m wrong.

It’s a theory based on this section in the docs.

Ohhh that makes more sense now! What would you suggest to wait for the least amount of time?

Right now, I wrote task.Wait(.1) right before I make a remote request, although I don’t know if that’s the best solution.

From my experience, replication and remotes are always ordered. So, if you parent an object to replicated storage, before sending it to the client through a remote, the client will receive it. However, if the object is parented after the remote is fired, the client will get nil

I don’t see anywhere in the code where block is parented? If its parent is nil, it wont replicate to the client

Another thing to consider is streaming enabled. If it is enabled, then objects in workspace may not replicate, and thus wont be accessible to the client

The best solution when you aren’t sure if an object will be available to the client or not, is to use CollectionService. Give a unique (or non unique depending on how you set it up) tag to the object, and with :GetInstanceAddedSignal() and :GetInstanceRemovedSignal(), you are able to detect when parts are received or removed (streamed out). Either send the tag through the remote, or it could be predetermined, …

1 Like

When you say to use collection service, are you referring to the AttachParts or the actual model?

I’m referring to any roblox object being sent through a remote, that you do not know if it exists on the client or not

I didn’t read the post fully yet :upside_down_face:

I don’t recommend waiting. Use what I suggested or what Client Side doesn't receive the same data the server has - #7 by Tomi1231 said to pick up when the instance is added, if replication is the issue.

Is this still an issue when the commented out line of block.Parent = blockFolder is uncommented? From the video it does seem like it, unless those operations are performed elsewhere?

Also, make sure Streaming Enabled is turned off (for testing at least. If you want streaming enabled, you’ll very likely have to track the parts with collection service)

The commented out line isn’t commented out in the actual game. I just removed it so that the code can be more readable. Also, I’ve researched on what streaming enabled is, but I still don’t really understand what it is. Can you explain what it does?

StreamingEnabled is a property under Workspace. When it is enabled, parts on the client are dynamically loaded in and out, depending on the distance from the character (or the replication focus, forgot how you modify that). This is useful for games with big maps, by allowing only the region around the player to be loaded in, reducing memory usage on the client. Roblox says it improves performance on the server, not sure by how much (it would probably reduce performance on the client as the client has to load things in and out instead of loading everything once)

https://create.roblox.com/docs/workspace/streaming

I gtg to sleep but I’ll reply once I have time.

1 Like

The first and the second problems are light, and the third one is critical.

Problem #1: BuildServiceServer.PlaceBlock. You’re cloning a block and then checking if it is placeable. Swap those two lines of code with each other, it will make more sense.

Problem #2: The same function. Please, create a variable for placementInfo.MouseObject and use this variable to get MouseObject later in the function. It will be more safe.

Huge Problem #3: You’re using “self.TotalAttachParts” and other values as general values for all players. Imagine 2 or more players editing the same data, and one of them is getting other player’s data. The solution is to create a blank table for each player who joined the game. And remove tables when a player leaves the game. Each table will have those default values: TotalAttachParts, etc. Don’t forget to get a player’s table when you’re calling .AddAttachparts (also pass a player as an arugment there), and .SetValue. +you can store all the players’ tables in a default-scoped local variable named playersDataBase (example name).

These are all the problems I saw based on the given info.

2 Likes

I never even thought about that.. I completely forgot that the server works differently than the client. :sob:

Would you recommend OOP for the server module or for both?

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