Advertisement
AlewAlow

Data Utilities

May 2nd, 2023 (edited)
337
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 13.46 KB | None | 0 0
  1. -------------------------
  2. ----- DataContainer
  3. -------------------------
  4.  
  5. local ReplicatedStorage = game:GetService("ReplicatedStorage")
  6.  
  7. local Signal = require(ReplicatedStorage.Libraries.Signal)
  8. local TableUtil = require(ReplicatedStorage.Libraries.TableUtil)
  9. local PathHelpers = require(ReplicatedStorage.Modules.Helpers.PathHelpers)
  10.  
  11. local DataContainer = {}
  12. DataContainer.__index = DataContainer
  13.  
  14. function DataContainer.new(data)
  15.     local self = setmetatable({}, DataContainer)
  16.     self.Data = data or {}
  17.     self.ValueChanged = Signal.new()
  18.     self._valueChangedSignals = {}
  19.    
  20.     return self
  21. end
  22.  
  23. function DataContainer:SetValue(path, newValue)
  24.     local path = PathHelpers.ConvertToPath(path)
  25.     local parent = PathHelpers.GetTargetByPath(self.Data, path)
  26.     local key = path[#path]
  27.    
  28.     if not parent or not key then
  29.         return
  30.     end
  31.    
  32.     local oldValue = parent[key]
  33.     if oldValue == newValue then
  34.         return
  35.     end
  36.    
  37.     parent[key] = newValue
  38.     self:_onValueChanged(path, newValue, oldValue)
  39. end
  40.  
  41. function DataContainer:_onValueChanged(path, newValue, oldValue)
  42.     local stringPath = PathHelpers.ConvertToString(path)
  43.     self.ValueChanged:Fire(stringPath, newValue, oldValue)
  44.    
  45.     if self._valueChangedSignals[stringPath] then
  46.         self._valueChangedSignals[stringPath]:Fire(newValue, oldValue)
  47.     end
  48. end
  49.  
  50. function DataContainer:InsertToTable(path, item)
  51.     local oldTable = self:GetValue(path)
  52.     if not oldTable then
  53.         return
  54.     end
  55.    
  56.     local newTable = TableUtil.Copy(oldTable)
  57.     table.insert(newTable, item)
  58.     self:SetValue(path, newTable)
  59. end
  60.  
  61. function DataContainer:RemoveFromTable(path, index)
  62.     local oldTable = self:GetValue(path)
  63.     if not oldTable then
  64.         return
  65.     end
  66.    
  67.     local newTable = TableUtil.Copy(oldTable)
  68.     table.remove(newTable, index)
  69.     self:SetValue(path, newTable)
  70. end
  71.  
  72. function DataContainer:GetValue(path)
  73.     local path = PathHelpers.ConvertToPath(path)
  74.     local parent = PathHelpers.GetTargetByPath(self.Data, path)
  75.     local key = path[#path]
  76.    
  77.     if not parent or not key then
  78.         return nil
  79.     end
  80.    
  81.     return parent[key]
  82. end
  83.  
  84. function DataContainer:GetValueWithPathChangedSignal(path)
  85.     local path = PathHelpers.ConvertToPath(path)
  86.     local stringPath = PathHelpers.ConvertToString(path)
  87.    
  88.     self._valueChangedSignals[stringPath] = self._valueChangedSignals[stringPath] or Signal.new()
  89.     return self._valueChangedSignals[stringPath]
  90. end
  91.  
  92. return DataContainer
  93.  
  94. -------------------------
  95. ----- DataHolder
  96. -------------------------
  97.  
  98. local ReplicatedStorage = game:GetService("ReplicatedStorage")
  99.  
  100. local Signal = require(ReplicatedStorage.Libraries.Signal)
  101. local Trove = require(ReplicatedStorage.Libraries.Trove)
  102. local Promise = require(ReplicatedStorage.Libraries.Promise)
  103. local TableUtil = require(ReplicatedStorage.Libraries.TableUtil)
  104.  
  105. local DEFAULT_TIMEOUT = 5
  106.  
  107. local DataHolder = {}
  108. DataHolder.__index = DataHolder
  109.  
  110. function DataHolder.new()
  111.     local self = setmetatable({}, DataHolder)
  112.     self._dataContainers = {}
  113.     self.DataContainerAdded = Signal.new()
  114.     self.DataContainerRemoved = Signal.new()
  115.    
  116.     return self
  117. end
  118.  
  119. function DataHolder:AddDataContainer(key, dataContainer)
  120.     self._dataContainers[key] = dataContainer
  121.     self.DataContainerAdded:Fire(key, dataContainer)
  122. end
  123.  
  124. function DataHolder:GetDataContainer(key)
  125.     return self._dataContainers[key]
  126. end
  127.  
  128. function DataHolder:WaitForKey(key, timeout)
  129.     local dataContainer = self:GetDataContainer(key)
  130.     if dataContainer then
  131.         return Promise.resolve(dataContainer)
  132.     end
  133.    
  134.     return Promise.fromEvent(self.DataContainerAdded, function(newKey, newDataContainer)
  135.         local match = key == newKey
  136.         if match then
  137.             dataContainer = newDataContainer
  138.         end
  139.        
  140.         return match
  141.     end):andThen(function()
  142.         return dataContainer
  143.     end):timeout(timeout or DEFAULT_TIMEOUT)
  144. end
  145.  
  146. function DataHolder:GetAllDataContainers()
  147.     return TableUtil.Copy(self._dataContainers)
  148. end
  149.  
  150. function DataHolder:RemoveDataContainer(key)
  151.     local dataContainer = self:GetDataContainer(key)
  152.     if not dataContainer then
  153.         return
  154.     end
  155.    
  156.     self._dataContainers[key] = nil
  157.     self.DataContainerRemoved:Fire(key, dataContainer)
  158. end
  159.  
  160. return DataHolder
  161.    
  162. -------------------------
  163. ----- DataSender
  164. -------------------------
  165.  
  166. local Players = game:GetService("Players")
  167. local ReplicatedStorage = game:GetService("ReplicatedStorage")
  168.  
  169. local Trove = require(ReplicatedStorage.Libraries.Trove)
  170. local Signal = require(ReplicatedStorage.Libraries.Signal)
  171. local TableUtil = require(ReplicatedStorage.Libraries.TableUtil)
  172.  
  173. local RemotesHelpers = require(ReplicatedStorage.Modules.Helpers.RemotesHelpers)
  174. local PathHelpers = require(ReplicatedStorage.Modules.Helpers.PathHelpers)
  175.  
  176. local DataSender = {}
  177. local DataContainerSender = {}
  178.  
  179. DataSender.__index = DataSender
  180. DataContainerSender.__index = DataContainerSender
  181.  
  182. function DataSender.new(replicationKey)
  183.     local self = setmetatable({}, DataSender)
  184.     self._trove = Trove.new()
  185.     self._dataContainersSenders = {}
  186.    
  187.     self._remotesFolder = script.Remotes:Clone()
  188.     self._remotesFolder.Parent = ReplicatedStorage.Remotes.DataReplication
  189.     self._remotesFolder.Name = replicationKey
  190.     self._trove:Add(self._remotesFolder)
  191.    
  192.     self.ReplicationKey = replicationKey
  193.     self.SenderCreated = self._trove:Add(Signal.new())
  194.    
  195.     self._trove:Add(self._remotesFolder.RequestInitialData.OnServerEvent:Connect(function(player)
  196.         local initialData = {}
  197.         for key, dataContainerSender in self._dataContainersSenders do
  198.             if dataContainerSender:CanReceive(player) then
  199.                 initialData[key] = dataContainerSender.DataContainer.Data
  200.             end
  201.         end
  202.        
  203.         self._remotesFolder.RequestInitialData:FireClient(player, initialData)
  204.     end))
  205.    
  206.     return self
  207. end
  208.  
  209. function DataSender:StartSendingDataContainer(key, dataContainer, receivers)
  210.     local object = setmetatable({}, DataContainerSender)
  211.     object._trove = self._trove:Add(Trove.new())
  212.     object._remotesFolder = self._remotesFolder
  213.    
  214.     object.Key = key
  215.     object.DataContainer = dataContainer
  216.    
  217.     object.Receivers = receivers
  218.     object.ReceiversIds = receivers and TableUtil.Map(receivers, function(player)
  219.         return player.UserId
  220.     end)
  221.    
  222.     self._dataContainersSenders[key] = object
  223.     object._trove:Add(function()
  224.         self._dataContainersSenders[key] = nil
  225.     end)
  226.    
  227.     object:_initReceiversValidators()
  228.     object:_startSendingData()
  229.    
  230.     return object
  231. end
  232.  
  233. function DataContainerSender:_initReceiversValidators()
  234.     if not self.Receivers then
  235.         return
  236.     end
  237.    
  238.     self._trove:Add(Players.PlayerAdded:Connect(function(player)
  239.         if not table.find(self.ReceiversIds, player.UserId) then
  240.             return
  241.         end
  242.  
  243.         table.insert(self.Receivers, player)
  244.     end))
  245.    
  246.     self._trove:Add(Players.PlayerRemoving:Connect(function(player)
  247.         local index = table.find(self.Receivers, player)
  248.         if not index then
  249.             return
  250.         end
  251.  
  252.         table.remove(self.Receivers, index)
  253.     end))
  254. end
  255.  
  256. function DataContainerSender:_startSendingData()
  257.     RemotesHelpers.FireSomeClientsOrAll(
  258.         self._remotesFolder.ReplicationStarted,
  259.         self.Receivers,
  260.         self.Key,
  261.         self.DataContainer.Data
  262.     )
  263.    
  264.     self._trove:Add(function()
  265.         RemotesHelpers.FireSomeClientsOrAll(
  266.             self._remotesFolder.ReplicationStopped,
  267.             self.Receivers,
  268.             self.Key
  269.         )
  270.     end)
  271.  
  272.     self._trove:Add(self.DataContainer.ValueChanged:Connect(function(path, newValue, oldValue)
  273.         RemotesHelpers.FireSomeClientsOrAll(
  274.             self._remotesFolder.ValueChanged,
  275.             self.Receivers,
  276.             self.Key,
  277.             path,
  278.             newValue
  279.         )
  280.     end))
  281. end
  282.  
  283. function DataContainerSender:CanReceive(player)
  284.     if not self.Receivers then
  285.         return true
  286.     end
  287.    
  288.     return table.find(self.ReceiversIds, player.UserId) ~= nil
  289. end
  290.  
  291. function DataContainerSender:Destroy()
  292.     self._trove:Destroy()
  293. end
  294.  
  295. function DataSender:StopSendingDataContainer(key)
  296.     local dataContainerSender = self._dataContainersSenders[key]
  297.     if dataContainerSender then
  298.         dataContainerSender:Destroy()
  299.     end
  300. end
  301.  
  302. function DataSender:Destroy()
  303.     self._trove:Destroy()
  304. end
  305.  
  306. return DataSender
  307.  
  308. -------------------------
  309. ----- DataReceiver
  310. -------------------------
  311.  
  312. local ReplicatedStorage = game:GetService("ReplicatedStorage")
  313.  
  314. local Signal = require(ReplicatedStorage.Libraries.Signal)
  315. local Trove = require(ReplicatedStorage.Libraries.Trove)
  316. local DataContainer = require(ReplicatedStorage.Modules.DataContainer)
  317.  
  318. local INITIAL_DATA_REQUEST_DELAY = 8
  319.  
  320. local DataReceiver = {}
  321. DataReceiver.__index = DataReceiver
  322.  
  323. function DataReceiver.new(dataHolder, replicationKey)
  324.     local self = setmetatable({}, DataReceiver)
  325.     self._trove = Trove.new()
  326.     self._remotesFolder = ReplicatedStorage.Remotes.DataReplication:WaitForChild(replicationKey)
  327.     self._trove:AttachToInstance(self._remotesFolder)
  328.    
  329.     self.DataHolder = dataHolder
  330.     self.ReplicationKey = replicationKey
  331.    
  332.     self.InitialDataLoaded = self._trove:Add(Signal.new())
  333.     self.HasLoadedInitialData = false
  334.    
  335.     self:_start()
  336.    
  337.     return self
  338. end
  339.  
  340. function DataReceiver:_start()
  341.     self._trove:Add(task.spawn(self._keepRequestingInitialData, self))
  342.     self._trove:Add(self._remotesFolder.RequestInitialData.OnClientEvent:Once(function(...)
  343.         self:_onInitialDataReceived(...)
  344.     end))
  345. end
  346.  
  347. function DataReceiver:_keepRequestingInitialData()
  348.     while not self.HasLoadedInitialData do
  349.         self._remotesFolder.RequestInitialData:FireServer()
  350.         task.wait(INITIAL_DATA_REQUEST_DELAY)
  351.     end
  352. end
  353.  
  354. function DataReceiver:_onInitialDataReceived(initialData)
  355.     for key, data in initialData do
  356.         self:_onDataContainerDataReceived(key, data)
  357.     end
  358.  
  359.     self:_startReceivingData()
  360.    
  361.     self.HasLoadedInitialData = true
  362.     self.InitialDataLoaded:Fire()
  363. end
  364.  
  365. function DataReceiver:_onDataContainerDataReceived(key, data)
  366.     local dataContainer = DataContainer.new(data)
  367.     self.DataHolder:AddDataContainer(key, dataContainer)
  368. end
  369.  
  370. function DataReceiver:_startReceivingData()
  371.     self._trove:Add(self._remotesFolder.ReplicationStarted.OnClientEvent:Connect(function(...)
  372.         self:_onDataContainerDataReceived(...)
  373.     end))
  374.    
  375.     self._trove:Add(self._remotesFolder.ReplicationStopped.OnClientEvent:Connect(function(key)
  376.         self.DataHolder:RemoveDataContainer(key)
  377.     end))
  378.    
  379.     self._trove:Add(self._remotesFolder.ValueChanged.OnClientEvent:Connect(function(key, path, value)
  380.         local dataContainer = self.DataHolder:GetDataContainer(key)
  381.         if not dataContainer then
  382.             return
  383.         end
  384.        
  385.         dataContainer:SetValue(path, value)
  386.     end))
  387. end
  388.  
  389. function DataReceiver:Destroy()
  390.     self._trove:Destroy()
  391. end
  392.  
  393. return DataReceiver
  394.  
  395.  
  396.  
  397.  
  398. -------------------------
  399. ----- PathHelpers
  400. -------------------------
  401.  
  402. local PathHelpers = {}
  403.  
  404. local PATH_SEPARATOR = "/"
  405.  
  406. function PathHelpers.GetTargetByPath(tbl, path)
  407.     local parent = tbl
  408.     for i = 1, #path - 1 do
  409.         local key = path[i]
  410.         local newParent = parent[key]
  411.         if newParent == nil then
  412.             return nil
  413.         end
  414.  
  415.         parent = newParent
  416.     end
  417.  
  418.     return parent
  419. end
  420.  
  421. function PathHelpers.ConvertToPath(value)
  422.     local valueType = type(value)
  423.  
  424.     if valueType == "table" then
  425.         return value
  426.     end
  427.  
  428.     if valueType == "string" then
  429.         return string.split(value, PATH_SEPARATOR)
  430.     end
  431.  
  432.     return nil
  433. end
  434.  
  435. function PathHelpers.ConvertToString(value)
  436.     local valueType = type(value)
  437.  
  438.     if valueType == "string" then
  439.         return value
  440.     end
  441.  
  442.     if valueType == "table" then
  443.         return table.concat(value, PATH_SEPARATOR)
  444.     end
  445.  
  446.     return nil
  447. end
  448.  
  449. function PathHelpers.PathsEqual(a, b)
  450.     local stringA = PathHelpers.ConvertToString(a)
  451.     local stringB = PathHelpers.ConvertToString(b)
  452.    
  453.     if not stringA or not stringB then
  454.         return false
  455.     end
  456.    
  457.     return stringA == stringB
  458. end
  459.  
  460. return PathHelpers
  461.  
  462.  
  463. -------------------------
  464. ----- RemotesHelpers
  465. -------------------------
  466.  
  467. local RemotesHelpers = {}
  468.  
  469. function RemotesHelpers.FireSomeClients(remoteEvent, clients, ...)
  470.     for _, client in clients do
  471.         remoteEvent:FireClient(client, ...)
  472.     end
  473. end
  474.  
  475. function RemotesHelpers.FireSomeClientsOrAll(remoteEvent, clients, ...)
  476.     if not clients then
  477.         remoteEvent:FireAllClients(...)
  478.         return
  479.     end
  480.  
  481.     RemotesHelpers.FireSomeClients(remoteEvent, clients, ...)
  482. end
  483.  
  484. return RemotesHelpers
  485.  
  486. -- example
  487. local Players = game:GetService("Players")
  488. local ReplicatedStorage = game:GetService("ReplicatedStorage")
  489. local RunService = game:GetService("RunService")
  490.  
  491. local DataContainer = require(ReplicatedStorage.Modules.DataContainer)
  492. local DataHolder = require(ReplicatedStorage.Modules.DataHolder)
  493.  
  494. local REPLICATION_KEY = "Players"
  495.  
  496. local DataManager = {}
  497. DataManager.DataHolder = DataHolder.new()
  498.  
  499. local function getPlayerKey(player)
  500.     return player.UserId
  501. end
  502.  
  503. if RunService:IsServer() then
  504.     local ServerScriptService = game:GetService("ServerScriptService")
  505.     local DataSender = require(ServerScriptService.Modules.DataSender)
  506.    
  507.     DataManager.DataSender = DataSender.new(REPLICATION_KEY)
  508.    
  509.     local function onPlayerAdded(player)
  510.         local key = getPlayerKey(player)
  511.         local dataContainer = DataContainer.new({
  512.             test1 = 1,
  513.             test2 = 2,
  514.             test3 = {
  515.                 test1 = 10,
  516.                 test2 = 20,
  517.             },
  518.         })
  519.        
  520.         local dataContainerSender = DataManager.DataSender:StartSendingDataContainer(
  521.             key,
  522.             dataContainer
  523.         )
  524.        
  525.         DataManager.DataHolder:AddDataContainer(key, dataContainer)
  526.     end
  527.    
  528.     for _, player in Players:GetPlayers() do
  529.         task.spawn(onPlayerAdded, player)
  530.     end
  531.    
  532.     Players.PlayerAdded:Connect(onPlayerAdded)
  533.     Players.PlayerRemoving:Connect(function(player)
  534.         local key = getPlayerKey(player)
  535.         DataManager.DataSender:StopSendingDataContainer(key)
  536.         DataManager.DataHolder:RemoveDataContainer(key)
  537.     end)
  538. else
  539.     local DataReceiver = require(ReplicatedStorage.Modules.DataReceiver)
  540.     DataManager.DataReceiver = DataReceiver.new(
  541.         DataManager.DataHolder,
  542.         REPLICATION_KEY
  543.     )
  544. end
  545.  
  546. return DataManager
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement