Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- -------------------------
- ----- DataContainer
- -------------------------
- local ReplicatedStorage = game:GetService("ReplicatedStorage")
- local Signal = require(ReplicatedStorage.Libraries.Signal)
- local TableUtil = require(ReplicatedStorage.Libraries.TableUtil)
- local PathHelpers = require(ReplicatedStorage.Modules.Helpers.PathHelpers)
- local DataContainer = {}
- DataContainer.__index = DataContainer
- function DataContainer.new(data)
- local self = setmetatable({}, DataContainer)
- self.Data = data or {}
- self.ValueChanged = Signal.new()
- self._valueChangedSignals = {}
- return self
- end
- function DataContainer:SetValue(path, newValue)
- local path = PathHelpers.ConvertToPath(path)
- local parent = PathHelpers.GetTargetByPath(self.Data, path)
- local key = path[#path]
- if not parent or not key then
- return
- end
- local oldValue = parent[key]
- if oldValue == newValue then
- return
- end
- parent[key] = newValue
- self:_onValueChanged(path, newValue, oldValue)
- end
- function DataContainer:_onValueChanged(path, newValue, oldValue)
- local stringPath = PathHelpers.ConvertToString(path)
- self.ValueChanged:Fire(stringPath, newValue, oldValue)
- if self._valueChangedSignals[stringPath] then
- self._valueChangedSignals[stringPath]:Fire(newValue, oldValue)
- end
- end
- function DataContainer:InsertToTable(path, item)
- local oldTable = self:GetValue(path)
- if not oldTable then
- return
- end
- local newTable = TableUtil.Copy(oldTable)
- table.insert(newTable, item)
- self:SetValue(path, newTable)
- end
- function DataContainer:RemoveFromTable(path, index)
- local oldTable = self:GetValue(path)
- if not oldTable then
- return
- end
- local newTable = TableUtil.Copy(oldTable)
- table.remove(newTable, index)
- self:SetValue(path, newTable)
- end
- function DataContainer:GetValue(path)
- local path = PathHelpers.ConvertToPath(path)
- local parent = PathHelpers.GetTargetByPath(self.Data, path)
- local key = path[#path]
- if not parent or not key then
- return nil
- end
- return parent[key]
- end
- function DataContainer:GetValueWithPathChangedSignal(path)
- local path = PathHelpers.ConvertToPath(path)
- local stringPath = PathHelpers.ConvertToString(path)
- self._valueChangedSignals[stringPath] = self._valueChangedSignals[stringPath] or Signal.new()
- return self._valueChangedSignals[stringPath]
- end
- return DataContainer
- -------------------------
- ----- DataHolder
- -------------------------
- local ReplicatedStorage = game:GetService("ReplicatedStorage")
- local Signal = require(ReplicatedStorage.Libraries.Signal)
- local Trove = require(ReplicatedStorage.Libraries.Trove)
- local Promise = require(ReplicatedStorage.Libraries.Promise)
- local TableUtil = require(ReplicatedStorage.Libraries.TableUtil)
- local DEFAULT_TIMEOUT = 5
- local DataHolder = {}
- DataHolder.__index = DataHolder
- function DataHolder.new()
- local self = setmetatable({}, DataHolder)
- self._dataContainers = {}
- self.DataContainerAdded = Signal.new()
- self.DataContainerRemoved = Signal.new()
- return self
- end
- function DataHolder:AddDataContainer(key, dataContainer)
- self._dataContainers[key] = dataContainer
- self.DataContainerAdded:Fire(key, dataContainer)
- end
- function DataHolder:GetDataContainer(key)
- return self._dataContainers[key]
- end
- function DataHolder:WaitForKey(key, timeout)
- local dataContainer = self:GetDataContainer(key)
- if dataContainer then
- return Promise.resolve(dataContainer)
- end
- return Promise.fromEvent(self.DataContainerAdded, function(newKey, newDataContainer)
- local match = key == newKey
- if match then
- dataContainer = newDataContainer
- end
- return match
- end):andThen(function()
- return dataContainer
- end):timeout(timeout or DEFAULT_TIMEOUT)
- end
- function DataHolder:GetAllDataContainers()
- return TableUtil.Copy(self._dataContainers)
- end
- function DataHolder:RemoveDataContainer(key)
- local dataContainer = self:GetDataContainer(key)
- if not dataContainer then
- return
- end
- self._dataContainers[key] = nil
- self.DataContainerRemoved:Fire(key, dataContainer)
- end
- return DataHolder
- -------------------------
- ----- DataSender
- -------------------------
- local Players = game:GetService("Players")
- local ReplicatedStorage = game:GetService("ReplicatedStorage")
- local Trove = require(ReplicatedStorage.Libraries.Trove)
- local Signal = require(ReplicatedStorage.Libraries.Signal)
- local TableUtil = require(ReplicatedStorage.Libraries.TableUtil)
- local RemotesHelpers = require(ReplicatedStorage.Modules.Helpers.RemotesHelpers)
- local PathHelpers = require(ReplicatedStorage.Modules.Helpers.PathHelpers)
- local DataSender = {}
- local DataContainerSender = {}
- DataSender.__index = DataSender
- DataContainerSender.__index = DataContainerSender
- function DataSender.new(replicationKey)
- local self = setmetatable({}, DataSender)
- self._trove = Trove.new()
- self._dataContainersSenders = {}
- self._remotesFolder = script.Remotes:Clone()
- self._remotesFolder.Parent = ReplicatedStorage.Remotes.DataReplication
- self._remotesFolder.Name = replicationKey
- self._trove:Add(self._remotesFolder)
- self.ReplicationKey = replicationKey
- self.SenderCreated = self._trove:Add(Signal.new())
- self._trove:Add(self._remotesFolder.RequestInitialData.OnServerEvent:Connect(function(player)
- local initialData = {}
- for key, dataContainerSender in self._dataContainersSenders do
- if dataContainerSender:CanReceive(player) then
- initialData[key] = dataContainerSender.DataContainer.Data
- end
- end
- self._remotesFolder.RequestInitialData:FireClient(player, initialData)
- end))
- return self
- end
- function DataSender:StartSendingDataContainer(key, dataContainer, receivers)
- local object = setmetatable({}, DataContainerSender)
- object._trove = self._trove:Add(Trove.new())
- object._remotesFolder = self._remotesFolder
- object.Key = key
- object.DataContainer = dataContainer
- object.Receivers = receivers
- object.ReceiversIds = receivers and TableUtil.Map(receivers, function(player)
- return player.UserId
- end)
- self._dataContainersSenders[key] = object
- object._trove:Add(function()
- self._dataContainersSenders[key] = nil
- end)
- object:_initReceiversValidators()
- object:_startSendingData()
- return object
- end
- function DataContainerSender:_initReceiversValidators()
- if not self.Receivers then
- return
- end
- self._trove:Add(Players.PlayerAdded:Connect(function(player)
- if not table.find(self.ReceiversIds, player.UserId) then
- return
- end
- table.insert(self.Receivers, player)
- end))
- self._trove:Add(Players.PlayerRemoving:Connect(function(player)
- local index = table.find(self.Receivers, player)
- if not index then
- return
- end
- table.remove(self.Receivers, index)
- end))
- end
- function DataContainerSender:_startSendingData()
- RemotesHelpers.FireSomeClientsOrAll(
- self._remotesFolder.ReplicationStarted,
- self.Receivers,
- self.Key,
- self.DataContainer.Data
- )
- self._trove:Add(function()
- RemotesHelpers.FireSomeClientsOrAll(
- self._remotesFolder.ReplicationStopped,
- self.Receivers,
- self.Key
- )
- end)
- self._trove:Add(self.DataContainer.ValueChanged:Connect(function(path, newValue, oldValue)
- RemotesHelpers.FireSomeClientsOrAll(
- self._remotesFolder.ValueChanged,
- self.Receivers,
- self.Key,
- path,
- newValue
- )
- end))
- end
- function DataContainerSender:CanReceive(player)
- if not self.Receivers then
- return true
- end
- return table.find(self.ReceiversIds, player.UserId) ~= nil
- end
- function DataContainerSender:Destroy()
- self._trove:Destroy()
- end
- function DataSender:StopSendingDataContainer(key)
- local dataContainerSender = self._dataContainersSenders[key]
- if dataContainerSender then
- dataContainerSender:Destroy()
- end
- end
- function DataSender:Destroy()
- self._trove:Destroy()
- end
- return DataSender
- -------------------------
- ----- DataReceiver
- -------------------------
- local ReplicatedStorage = game:GetService("ReplicatedStorage")
- local Signal = require(ReplicatedStorage.Libraries.Signal)
- local Trove = require(ReplicatedStorage.Libraries.Trove)
- local DataContainer = require(ReplicatedStorage.Modules.DataContainer)
- local INITIAL_DATA_REQUEST_DELAY = 8
- local DataReceiver = {}
- DataReceiver.__index = DataReceiver
- function DataReceiver.new(dataHolder, replicationKey)
- local self = setmetatable({}, DataReceiver)
- self._trove = Trove.new()
- self._remotesFolder = ReplicatedStorage.Remotes.DataReplication:WaitForChild(replicationKey)
- self._trove:AttachToInstance(self._remotesFolder)
- self.DataHolder = dataHolder
- self.ReplicationKey = replicationKey
- self.InitialDataLoaded = self._trove:Add(Signal.new())
- self.HasLoadedInitialData = false
- self:_start()
- return self
- end
- function DataReceiver:_start()
- self._trove:Add(task.spawn(self._keepRequestingInitialData, self))
- self._trove:Add(self._remotesFolder.RequestInitialData.OnClientEvent:Once(function(...)
- self:_onInitialDataReceived(...)
- end))
- end
- function DataReceiver:_keepRequestingInitialData()
- while not self.HasLoadedInitialData do
- self._remotesFolder.RequestInitialData:FireServer()
- task.wait(INITIAL_DATA_REQUEST_DELAY)
- end
- end
- function DataReceiver:_onInitialDataReceived(initialData)
- for key, data in initialData do
- self:_onDataContainerDataReceived(key, data)
- end
- self:_startReceivingData()
- self.HasLoadedInitialData = true
- self.InitialDataLoaded:Fire()
- end
- function DataReceiver:_onDataContainerDataReceived(key, data)
- local dataContainer = DataContainer.new(data)
- self.DataHolder:AddDataContainer(key, dataContainer)
- end
- function DataReceiver:_startReceivingData()
- self._trove:Add(self._remotesFolder.ReplicationStarted.OnClientEvent:Connect(function(...)
- self:_onDataContainerDataReceived(...)
- end))
- self._trove:Add(self._remotesFolder.ReplicationStopped.OnClientEvent:Connect(function(key)
- self.DataHolder:RemoveDataContainer(key)
- end))
- self._trove:Add(self._remotesFolder.ValueChanged.OnClientEvent:Connect(function(key, path, value)
- local dataContainer = self.DataHolder:GetDataContainer(key)
- if not dataContainer then
- return
- end
- dataContainer:SetValue(path, value)
- end))
- end
- function DataReceiver:Destroy()
- self._trove:Destroy()
- end
- return DataReceiver
- -------------------------
- ----- PathHelpers
- -------------------------
- local PathHelpers = {}
- local PATH_SEPARATOR = "/"
- function PathHelpers.GetTargetByPath(tbl, path)
- local parent = tbl
- for i = 1, #path - 1 do
- local key = path[i]
- local newParent = parent[key]
- if newParent == nil then
- return nil
- end
- parent = newParent
- end
- return parent
- end
- function PathHelpers.ConvertToPath(value)
- local valueType = type(value)
- if valueType == "table" then
- return value
- end
- if valueType == "string" then
- return string.split(value, PATH_SEPARATOR)
- end
- return nil
- end
- function PathHelpers.ConvertToString(value)
- local valueType = type(value)
- if valueType == "string" then
- return value
- end
- if valueType == "table" then
- return table.concat(value, PATH_SEPARATOR)
- end
- return nil
- end
- function PathHelpers.PathsEqual(a, b)
- local stringA = PathHelpers.ConvertToString(a)
- local stringB = PathHelpers.ConvertToString(b)
- if not stringA or not stringB then
- return false
- end
- return stringA == stringB
- end
- return PathHelpers
- -------------------------
- ----- RemotesHelpers
- -------------------------
- local RemotesHelpers = {}
- function RemotesHelpers.FireSomeClients(remoteEvent, clients, ...)
- for _, client in clients do
- remoteEvent:FireClient(client, ...)
- end
- end
- function RemotesHelpers.FireSomeClientsOrAll(remoteEvent, clients, ...)
- if not clients then
- remoteEvent:FireAllClients(...)
- return
- end
- RemotesHelpers.FireSomeClients(remoteEvent, clients, ...)
- end
- return RemotesHelpers
- -- example
- local Players = game:GetService("Players")
- local ReplicatedStorage = game:GetService("ReplicatedStorage")
- local RunService = game:GetService("RunService")
- local DataContainer = require(ReplicatedStorage.Modules.DataContainer)
- local DataHolder = require(ReplicatedStorage.Modules.DataHolder)
- local REPLICATION_KEY = "Players"
- local DataManager = {}
- DataManager.DataHolder = DataHolder.new()
- local function getPlayerKey(player)
- return player.UserId
- end
- if RunService:IsServer() then
- local ServerScriptService = game:GetService("ServerScriptService")
- local DataSender = require(ServerScriptService.Modules.DataSender)
- DataManager.DataSender = DataSender.new(REPLICATION_KEY)
- local function onPlayerAdded(player)
- local key = getPlayerKey(player)
- local dataContainer = DataContainer.new({
- test1 = 1,
- test2 = 2,
- test3 = {
- test1 = 10,
- test2 = 20,
- },
- })
- local dataContainerSender = DataManager.DataSender:StartSendingDataContainer(
- key,
- dataContainer
- )
- DataManager.DataHolder:AddDataContainer(key, dataContainer)
- end
- for _, player in Players:GetPlayers() do
- task.spawn(onPlayerAdded, player)
- end
- Players.PlayerAdded:Connect(onPlayerAdded)
- Players.PlayerRemoving:Connect(function(player)
- local key = getPlayerKey(player)
- DataManager.DataSender:StopSendingDataContainer(key)
- DataManager.DataHolder:RemoveDataContainer(key)
- end)
- else
- local DataReceiver = require(ReplicatedStorage.Modules.DataReceiver)
- DataManager.DataReceiver = DataReceiver.new(
- DataManager.DataHolder,
- REPLICATION_KEY
- )
- end
- return DataManager
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement