Jump to content

[HELP] Optimized Ped Sync


Recommended Posts

Hello everyone!

Usually when I need to sync peds for every player, I have to call triggerClientEvent(getRootElement(), 'someEvent'), but is it right? I've read that every entity (ped, player, vehicle) have an a syncer which is syncing data and tells server about it -> server tells other clients... and I've tried to call triggerClientEvent(getElementSyncer(ped), 'someEvent'), but when I connect other client, only syncer sees ped control state (forwards, in my case), but second player sees ped only changing his pos and Z-rot and no control state changing..

So the question is: I have to trigger event for every client, or the other way exists?

Link to comment
  • Moderators
45 minutes ago, Thomas_Nightfire said:

but is it right? I've read that every entity (ped, player, vehicle) have an a syncer which is syncing data and tells server about it -> server tells other clients...

The syncer (which can be anybody as long as the ped is streamed in for that player) will synchronized the following:

  • Position
  • Rotation
  • Velocity < probably
  • ??? unknown stuff, I did not test everything ???

 

The most other things are not synchronized.

For example:

  • Control state (triggerClientSide/elementData required)
  • Model (unless set by serverside)
  • Dimension/interior (unless set by serverside)

 

See next page


 

 

45 minutes ago, Thomas_Nightfire said:

So the question is: I have to trigger event for every client, or the other way exists?

You have to keep track of the ped control states on serverside. You can use tables for that or even elementData, just pick what you can work with.

 

 

But you probably want to start with keeping track of loaded players. The following code is used in the beta resource that I shared not long ago.

local players = getLoadedPlayers() -- table {player, player, player}

triggerClientEvent (players, "event-name", ped, "walk", true)

---------------------

local loadedStatus = isPlayerLoaded (player) -- true/false

 

 

Client

Spoiler


clientResourceStart = function ()
	triggerServerEvent("onClientPlayerResourceStart", resourceRoot)
end
addEventHandler("onClientResourceStart", resourceRoot, clientResourceStart, false)

 

 

Server

Spoiler


local loadedPlayers = dataManagerConstructor:new()

addEvent("onClientPlayerResourceStart", true)
addEventHandler("onClientPlayerResourceStart", resourceRoot, 
function () 
	if isElement(client) then
		loadedPlayers:setData(client, getTickCount())
	end
end, false)

addEventHandler("onPlayerQuit", root, 
function ()
	loadedPlayers:removeData(source)
end)

function getLoadedPlayers()
	return loadedPlayers:getKeys()
end

function isPlayerLoaded (player)
	return loadedPlayers:getData(player) and true or false
end

 

 

My dataManagement Library used in the script above, might be a little bit complex but feel free to use:

Spoiler


dataManagerConstructor = {
	new = function (self)
		local dataManager = {
			collection = {}, -- Optimised for fast loops
			registery = {} -- Optimised to speed up finding the right item
		}
		
		local methods = self.methods
		-- The following can also be done with just a meta table, but that would make the methods slower. Making an instance is just something you do once.
		for i=1, #methods do
			local method = methods[i]
			dataManager[method.key] = method.func
		end
		
		return dataManager
	end,
	methods = {		
		{
			key = "clone",
			func = function (self) 
				local newCollection = {}
				local newRegistery = {}
				
				local dataManager = {
					collection = newCollection,
					registery = newRegistery
				}
				
				local collection = self.collection
				for i=1, #collection do
					local item = collection[i]
					local key = item.key
					local newItem = {
						key  = key,
						value = item.value
					}
					newCollection[i] = newItem
					newRegistery[key] = newItem
				end
				
				local methods = dataManagerConstructor.methods
				for i=1, #methods do
					local method = methods[i]
					dataManager[method.key] = method.func
				end
				
				return dataManager 
			end
		},
		{
			key = "setData",
			func = function (self, key, value)
				local registery = self.registery
				if key ~= nil and not registery[key] then
					local item = {
						key = key,
						value = value
					}
					registery[key] = item
					local collection = self.collection
					collection[#collection + 1] = item
					return self, true
				end
				return self, false
			end
		},
		{
			key = "getData",
			func = function (self, key)
				if key ~= nil then
					local item = self.registery[key]
					if item then
						return item.value
					end
				end
				return false
			end
		},
		{
			key = "clear", 
			func = function (self)
				self.collection = {}
				self.registery = {}
				return self
			end
		},
		{
			key = "getNext",
			func = function (self, index)
				local collection = self.collection
				if index == 0 then
					index = nil
				end
				local foundIndex, item = next(collection, index)
				if item then
					return item.key, item.value, foundIndex
				end
			end
		},
		{
			key = "forEach",
			func = function (self, callBackFunc, inverted)
				local collection = self.collection
				if not inverted then
					for i=1, #collection do
						local item = collection[i]
						callBackFunc(item.key, item.value)
					end
				else
					for i=#collection, 1, -1 do
						local item = collection[i]
						callBackFunc(item.key, item.value)
					end
				end
				return true
			end
		},
		{
			key = "getKeys",
			func = function (self)
				local keys = {}
				local collection = self.collection
				for i=1, #collection do
					keys[i] = collection[i].key
				end
				return keys
			end
		},		
		{
			key = "sort",
			func = function (self, func)
				
				local collection = self.collection
				table.sort(collection, func)
				return true
			end
		},
		{
			key = "getAllData",
			func = function (self, reduceData)
				local collection = self.collection
				-- Format: {key = ..., value = ...}
				local newCollection = {}
				for i=1, #collection do
					local item = collection[i]
					if reduceData and type(reduceData) == "function" then
						
						newCollection[#newCollection + 1] = {key = item.key, value = reduceData(item.value)}
					else
						newCollection[#newCollection + 1] = {key = item.key, value = item.value}
					end
				end
				return newCollection
			end
		},
		{
			key = "addAllData",
			func = function (self, collection)
				for i=1, #collection do
					local item = collection[i]
					self:setData(item.key, item.value)
				end
				return self, true
			end
		},
		{
			key = "removeData",
			func = function (self, key)
				local registery = self.registery
				if key ~= nil then
					local item = registery[key]
					if item then
						registery[key] = nil
						local collection = self.collection
						for i=1, #collection do
							if collection[i] == item then
								table.remove(collection, i)
								break
							end
						end
						return self, true
					end
				end
				return self, false
			end
		},
	}
}

 

 

 

 

 

 

Edited by IIYAMA
  • Thanks 1
Link to comment
Quote

You have to keep track of the ped control states on serverside

You mean set data to ped server-side, and with timer / other often repeating event check this data and do actions on client?

 

Quote

But you probably want to start with keeping track of loaded players

Loaded players? A variable root isn't include all players currently online? There aren't the same? I'm sorry, I think I do not understand MTA Tree System...

Link to comment
  • Moderators
1 hour ago, Thomas_Nightfire said:

You mean set data to ped server-side, and with timer / other often repeating event check this data and do actions on client?

No, when for example a control state has changed, you keep that information on the server. With this information you can inform new/not loaded clients about the current states of the peds.

pedDataCollection = {}

-- set-up

pedDataCollection[ped] = {
	running = false
}

 

local pedData = pedDataCollection[ped]

pedData.running = true

 

It can also reduce data tranfers between the client and the server.

So if you know that you set the ped to run, you do not have to tell all clients later again that the ped is running.

 

 

1 hour ago, Thomas_Nightfire said:

Loaded players? A variable root isn't include all players currently online? There aren't the same? I'm sorry, I think I do not understand MTA Tree System...

It is, but it gives error when a client has not loaded their resources. Which is in my opinion not very clean. You want to reduce errors to the minimal to prevent lag.

 

Edited by IIYAMA
  • Thanks 1
Link to comment
7 hours ago, IIYAMA said:

It is, but it gives error when a client has not loaded their resources. Which is in my opinion not very clean. You want to reduce errors to the minimal to prevent lag.

He can have a variable client sided like:

local isLoggedIn = false;

if isLoggedIn then

 ...bla bla bla

end

and only manage peds for players that have this variable true and when login set the variable true, this should work, right?

  • Like 1
Link to comment
  • Moderators
4 hours ago, Tekken said:

and only manage peds for players that have this variable true and when login set the variable true, this should work, right?

Yes that could work in terms of ped management. But that does not solve the hard coded error, that will occur when the client has not added the <event> yet, while receiving <that event> from serverside. It can even lead to desync when serverside is not sure if the client has received it's message. Sure you can repeatedly send the same data, but that will be an unnecessary data transfers (in my opinion).

You want to start the dialogue between clientside and serverside when you know that both sides are ready, so that your resource can be optimized for data reduction.

  • Thanks 1
Link to comment
On 07/02/2021 at 16:42, IIYAMA said:

Yes that could work in terms of ped management. But that does not solve the hard coded error, that will occur when the client has not added the <event> yet, while receiving <that event> from serverside. It can even lead to desync when serverside is not sure if the client has received it's message. Sure you can repeatedly send the same data, but that will be an unnecessary data transfers (in my opinion).

You want to start the dialogue between clientside and serverside when you know that both sides are ready, so that your resource can be optimized for data reduction.

Thanks a lot, undertood. But to not make another topic, how can I update peds frequently? I mean serverside timer with ~100ms will be bad, like u said earlier. I've made a resource with NPCs and it works with 200ms-timer and it's a bit laggy even playing with my friend on local network. May be I need to use onClientPreRender or clientside timer to check state of peds? But I think it would be better to check their states serverside to avoid any desync...

P.S.: like in CrystalMV's NPC HLC Traffic - 200 ms for serverside 'lightweight' update for unsynced peds & onClientRender for synced

Edited by Thomas_Nightfire
Link to comment
  • Moderators
18 hours ago, Thomas_Nightfire said:

Thanks a lot, undertood. But to not make another topic, how can I update peds frequently? I mean serverside timer with ~100ms will be bad, like u said earlier. I've made a resource with NPCs and it works with 200ms-timer and it's a bit laggy even playing with my friend on local network. May be I need to use onClientPreRender or clientside timer to check state of peds? But I think it would be better to check their states serverside to avoid any desync...

P.S.: like in CrystalMV's NPC HLC Traffic - 200 ms for serverside 'lightweight' update for unsynced peds & onClientRender for synced

  • The data rate is indeed an issue. I capped my own ped resource on 300ms.
  • But the quantity also plays a role. It is bad practice to send the same information twice. If the client knows a ped is running, the server does not have to send that information again.
  • If there are no peds around you, the data rate by this resource should be nearly 0.

 

19 hours ago, Thomas_Nightfire said:

P.S.: like in CrystalMV's NPC HLC Traffic - 200 ms for serverside 'lightweight' update for unsynced peds & onClientRender for synced

Are you sure that it sends data on the onClientRender rate?

 

 

Link to comment

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...