Datastore module

Hi. I have recently created this simple datastore class:

-- Data Class
-- UntitledProtocol
-- July 24, 2020



local DataClass = {}
DataClass.__index = DataClass

local FETCH_CHANCES = 3
local SAVE_CHANCES = 3
local COOLDOWN = 6

local DataStoreService = game:GetService("DataStoreService")

--[[
    MODULE
]]

function DataClass.create(name, player, defaultData)
    local self = setmetatable({}, DataClass)
    self._data = defaultData
    self._dataStore = DataStoreService:GetDataStore(name)
    self._player = player

    self._fetchRawDataQueue = Instance.new("BindableEvent") -- The event which will fire once a call of the fetch raw data has been ran
    self._fetchRawDataWasRan = false -- Indicates if _fetchRawData function has been ran for the first time
    self._fetchingRawData = false

    self._savingQueue = Instance.new("BindableEvent")
    self._savingEnabled = false
    self._saving = false

    self.unableToSave = Instance.new("BindableEvent") -- Fires if a save call was unsuccessfull
    self.unableToFetch = Instance.new("BindableEvent") -- Fires if a _fetchRawData call was unsuccessfull

    return self
end

--[[
    PRIVATE API
]]

function DataClass:_fetchRawData()
    if (self._fetchingRawData) then
        warn("Another _fetchRawData call is already occurring. Returning current call when finished.")
        self._fetchRawDataQueue.Event:Wait()
        return
    end

    self._fetchingRawData = true

    local success
    local err

    for _ = 1, FETCH_CHANCES, 1 do
        local data
        success, err = pcall(function()
            data = self._dataStore:GetAsync(self._player.UserId)
        end)
        
        if (success) then
            self._data = data or self._data -- If the player is new, we don't want nil to overwrite our default value
            break
        end

        wait(COOLDOWN)
    end

    if not success then
        warn("_fetchRawData called failed:", err)
        self.unableToFetch:Fire()
    end

    self._savingEnabled = success
    self._fetchRawDataWasRan = true
    self._fetchingRawData = false
    self._fetchRawDataQueue:Fire()
end


--[[
    PUBLIC API
]]

function DataClass:Save()
    if (not self._savingEnabled) then warn("Saving disabled.") return end

    if (self._saving) then
        warn("A save call is already in progress, waiting until completed.")
        self._savingQueue.Event:Wait()
        return
    end

    self._data.ARB = (self._data.ARB or 0) + 1 -- Increment the anti rollback value
    self._saving = true

    local success
    local err

    for _ = 1, SAVE_CHANCES, 1 do
        success, err = pcall(self._dataStore.UpdateAsync, self._dataStore, self._player.UserId, function(oldData)
            if (not self.oldData) or (self._data.ARB > oldData.ARB) then
                return self._data
            else
                self._savingEnabled = false
                self._player = self._player:Kick("Anti rollback value is lesser than or equal to old antirollback value. Please rejoin after a few minutes.")
                return oldData
            end
        end)

        if success then
            break
        end

        wait(COOLDOWN)
    end

    if not success then
        self.unableToSave:Fire(err)
    end

    self._saving = false
    self._savingQueue:Fire()
end

function DataClass:GetAllData()
    if (not self._fetchRawDataWasRan) then
        self:_fetchRawData()
    end

    return self.data
end

function DataClass:GetDataComponent(name)
    if (not self._fetchRawDataWasRan) then
        self:_fetchRawData()
    end

    return self.data[name]
end

function DataClass:CacheDataComponent(name, value)
    if (not self._fetchRawDataWasRan) then
        self:_fetchRawData()
    end

    self._data[name] = value
end

return DataClass

Although it works, I feel like the code is starting to become difficult to navigate through and change. Therefore, future edits of this may cause unnecessary amounts of time figuring out what is happening. Any suggestions on how I can simplify are very appreciated. Thanks.

3 Likes

The only suggestion I will say is: you need to write more comments. It is ok to have comments everywhere in your code. I personally have many comments telling me what each section of my script does. This will help you in locating errors faster.

1 Like