Seemingly illogical error with datastorage

So I have this module, and whenever I try to call this function:

(around line 23)

-- Read data function
function PlayerStatManager:GetData(player, statName)
    warn("DATA : ",unpack(sessionData or {}))
    return sessionData[player.Name][statName]
end

it gives me this error:

SaveDataModule:23: attempt to index field ‘?’ (a nil value)

This is the entire module. It is a modified version of the one on the official Saving Player Data Roblox wiki page

-- Setup table that we will return to scripts that require the ModuleScript.
PlayerStatManager = {}
 
-- Create variable for the DataStore.
DataStoreService = game:GetService('DataStoreService')
playerData = DataStoreService:GetDataStore('PlayerData2'.. game.PlaceId )

-- Create variable to configure how often the game autosaves the player data.
AUTOSAVE_INTERVAL = 60
 
-- Table to hold all of the player information for the current session.
sessionData = {}
 
-- Function the other scripts in our game can call to change a player's stats. This
-- function is stored in the returned table so external scripts can use it.
function PlayerStatManager:ChangeStat(player, statName, changeValue)
	sessionData[player.Name][statName] = sessionData[player.Name][statName] + changeValue
end

-- Read data function
 function PlayerStatManager:GetData(player, statName)
	warn("DATA : ",unpack(sessionData or {}))
	return sessionData[player.Name][statName]
end

-- Function to retrieve player's data from the DataStore.
local function getPlayerData(player)
	return playerData:GetAsync(player.UserId)
end
 
-- Function to save player's data to the DataStore.
local function savePlayerData(player)
	playerData:SetAsync(player.UserId, sessionData[player.Name])
end
 
-- Function to add player to the sessionData table. First check if the player has
-- data in the DataStore. If so, we'll use that. If not, we'll add the player to
-- the DataStore.
local function setupPlayerData(player)
	local data = getPlayerData(player)
	if not data then
		-- DataStores are working, but no data for this player
		sessionData[player.Name] = {Money = 0,
							   MaxHealth = 100,
							   MaxShield = 100,
							   ["tools"] = {
								 -- {"Sword","Classic sword",{}},
								 {"Ax","Classic ax",{"Speedy","Strong"},{2,3}}
							   },
							   ["items"] = {
								 {"Wood",5,"The stuff of trees"},
								 {"Fish",3,"a limbless animal with gills and fins"}
							   }
								
							  }

		savePlayerData(player)
	else
		-- DataStores are working and we got data for this player
		sessionData[player.Name] = data
	end
end
 
-- Function to run in the background to periodically save player's data.
local function autosave()
	while wait(AUTOSAVE_INTERVAL) do
		for player, data in pairs(sessionData) do
			savePlayerData(player)
		end
	end
end
 
-- Bind setupPlayerData to PlayerAdded to call it when player joins.
game.Players.PlayerAdded:connect(setupPlayerData)
 
-- Call savePlayerData on PlayerRemoving to save player data when they leave.
-- Also delete the player from the sessionData, as the player isn't in-game anymore.
game.Players.PlayerRemoving:connect(function(player)
	savePlayerData(player)
	sessionData[player.Name] = nil
end)
 
-- Start running autosave function in the background.
spawn(autosave)
 
-- Return the PlayerStatManager table to external scripts can access it.
return PlayerStatManager


-- PlayerStatManager:ChangeStat(player, statName, changeValue)
-- PlayerStatManager:GetData(player, statName)
-- https://www.roblox.com/Thumbs/Asset.ashx?width=110&height=110&assetId=1818

what is annoying me is the fact that the script errors because it thinks sessionData is undefined when I try to call it in

return sessionData[player.Name][statName]

and sessionData is clearly defined a few lines above

I’ve been having this problem for a while now.
Originally, I posted this on the script helpers forum

But that post seems to be dead and inactive, so I thought I would post on this new fancy schmancy official Roblox developers forum.

Also, I would like to point out that

  1. warn is a built-in Roblox function, similar to print
  2. Unpack is a built-in Roblox function as well (it is the same as doing return list[i], list[i+1], ···, list[j])
  3. There is nothing wrong with the way I am concatenating
  4. This will still error without the warn. The problem is with sessionData being a “nil value”

This is for my game, Sorcery

1 Like

Try checking the value of sessionData - if it’s nil, find out why.

If it should be nil, add a or {} to the unpack statement so it’ll replace sessionData with an empty table when it tries to unpack.

e.g.
func(value or {})

I expect this error has something to do with the unpack function, not the warn.

Thanks for the quick response!

I already know that sessionData is nil.
That is why I used that warn function.
I am just unable to figure out why it is nil.
sessionData is clearly defined right above where the warn function is.

1 Like

It’s the warn line where you are getting the error, right?

Just testing out the function on a new place, it seems to throw a different error with a nil value in the unpack.

This is quite an odd issue…

My final idea is that this is actually an error somewhere else, but due to it being on a different (psuedo)thread – via coroutines, signal callbacks or spawn – it actually gives you the wrong location in the output.

Yeah, this is weird.

Also, I did an edit and changed the unpack line to

warn("DATA : ",unpack(sessionData or {}))

like you said.

I’m going to edit the post to make the problem a little more clear cut and easy to understand.

1 Like

Make sure you’re calling it as PlayerStatManager:GetData() instead of PlayerStatManager.GetData() (notice: : vs .). Remember that when you define a method with a :, it expects itself as its first argument.

Besides, It’s not that statName is nil, it’s that that player.Name is nil.

local sessionData = {}
local player = {}
local statName = "A valid statName"
print(sessionData[player.Name][statName]) -- attempt to index field '?' (a nil value)

EDIT: This example should be clearer:

-- dummy session data
local sessionData = {
    Player1 = {
        Gold = 1234
    }
}

local PlayerStatManager = {}
function PlayerStatManager:GetData(player, statName)
    return sessionData[player.Name][statName]
end

-- dummy variables
local player = {Name = "Player1"}
local statName = "Gold"

-- Calling it with a `:`
local gold = PlayerStatManager:GetData(player, statName)
print(gold) -- prints "1234"

-- Calling it with a `.`
local _, err = pcall(function()
    local gold = PlayerStatManager.GetData(player, statName)
    print(gold)
end)
print(err) -- prints "attempt to index field '?' (a nil value)"
2 Likes

Yeah, I made sure to use : instead of . in the localscript.
In fact, I made this function in the localscript to getdata

GetDataRequest = ReplicatedStorage:WaitForChild("GetDataRequest")
function getData(request)
    return GetDataRequest:InvokeServer(request)
end

This script accesses DataStores, so surely this module is ServerSide?

In the server, what responds to the request?

To be very clear, make sure you’re calling the module with :GetData instead of .GetData

1 Like

Oh yeah, I forgot to include that.

-- dispeller

DataModule = require(game:GetService("ServerScriptService").SaveDataModule)

ReplicatedStorage = game:GetService("ReplicatedStorage")
 
GetDataRequest = Instance.new("RemoteFunction")
GetDataRequest.Parent = ReplicatedStorage
GetDataRequest.Name = "GetDataRequest"

ImageModule = require(game:GetService("ServerScriptService").ItemImageModule)
 
GetDataRequest.OnServerInvoke = function (player,request)
	if request == "images" then return ImageModule end
	return DataModule:GetData(player.Name,request)
end

Ignore the images part of that.
It is irrelevant, I just decided to be lazy and re-use the same RemoteFunction to get two different things.
The images library and the Player Data.

Oh. You’re calling it as DataModule:GetData(**player.Name**,request), but inside the :GetData() method, you’re doing sessionData[**player.Name**][statName] again. When you index a string, it returns nil.

Call it as DataModule:GetData(player,request) in the RemoteFunction’s callback.

2 Likes

Thank you!