"attempt to call missing method of table" from module object

I have a module for storing player stats called PlayerData. The module is made for created objects following the model described in All about Object Oriented Programming - Resources / Community Tutorials - DevForum | Roblox

When a player joins the game, I attempt to load their data from a DataStore. If nothing is loaded, a new PlayerData object is created for them. That object(table) is what gets saved to the DataStore. Through the DataStore Editor plugin I can see that saving this data works for saving the data defined in the PlayerData.new function.

My issue is that I get the PlayerData objects through a bindable function but when I try to call a function from it I get “Attempt to call missing method “IncreaseDefense” of table.” Values defined in PlayerData.new can be referenced without issue. I set the metatable of the table created by PlayerData.new as it is done in the above post, so I do not understand why the functions cannot be found. Any assistance would be appreciated

PlayerData module

local PlayerData = {}
PlayerData.__index = PlayerData

-- Create new PlayerData object
function PlayerData.new()
	local self = setmetatable({}, PlayerData)
	self.strength = 3
	self.defense = 0
	self.speed = 0
	self.strengthOnClick = 1
	self.defenseOnClick = 1
	self.speedOnClick = 1
	return self
end

-- Get total power as combination of 3 primary stats
function PlayerData:GetTotalPower()
	return self.strength + self.defense + self.speed
end

-- Increase strength by strength on click value
function PlayerData:IncreaseStrength()
	self.strength += self.strengthOnClick
	return self.strength
end

-- Increase defense by defense on click value
function PlayerData:IncreaseDefense()
	self.defense += self.defenseOnClick
	return self.defense
end

-- Increase speed by speed on click value
function PlayerData:IncreaseSpeed()
	self.speed += self.speedOnClick
	return self.speed
end

return PlayerData

Script handling loading/saving data and bindable function

local DataStore = game:GetService("DataStoreService"):GetDataStore("UnlimitedPower")
local data = {}
local PlayerData = require(game:GetService("ServerScriptService").Modules.PlayerData)
local BindableFunctions = game:GetService("ServerStorage").BindableFunctions

game.Players.PlayerAdded:Connect(function(player)
	-- Get Player id
	local id = "Player_" .. player.UserId
	-- Load data
	local success, errormessage = pcall(function()
		data[id] = DataStore:GetAsync(id)
	end)
	if(data[id] == nil) then
		data[id] = PlayerData.new()
	end
	print("Strength: " .. data[id].strength) -- Prints 3
end)

game.Players.PlayerRemoving:Connect(function(player)
	-- Get Player id
	local id = "Player_" .. player.UserId
	-- Save data
	local success, errormessage = pcall(function()
		DataStore:SetAsync(id, data[id])
	end)
	-- Remove from data table
	data[id] = nil
end)

BindableFunctions.GetPlayerData.OnInvoke = function(player)
	local id = "Player_" .. player.UserId
	return data[id]
end

Script attempting to get PlayerData object and call functions

local RemoteFunctions = game:GetService("ReplicatedStorage").RemoteFunctions
local BindableFunctions = game:GetService("ServerStorage").BindableFunctions
-- Strength button function
RemoteFunctions.StrengthClick.OnServerInvoke = function(player)
	local PlayerData = BindableFunctions.GetPlayerData:Invoke(player)
	print(PlayerData.strength) -- Prints 3
end
-- Defense button function
RemoteFunctions.DefenseClick.OnServerInvoke = function(player)
	local PlayerData = BindableFunctions.GetPlayerData:Invoke(player)
	local newDefense = PlayerData:IncreaseDefense() -- "attempt to call missing method 'IncreaseDefense' of table"
	print("Defense: " .. newDefense)
	return newDefense
end
-- Speed button function
RemoteFunctions.SpeedClick.OnServerInvoke = function(player)
	local PlayerData = BindableFunctions.GetPlayerData:Invoke(player)
	local newSpeed = PlayerData:IncreaseSpeed() -- "attempt to call missing method 'IncreaseSpeed' of table"
	print("Speed: " .. newSpeed)
	return newSpeed
end

To narrow down the problem a bit more, I found that doing print(self.getmetatable()) in PlayerData.new after setting the metatable causes an error “attempt to call a nil value”

So my issue here is setmetatable not working correctly. I looked at another project of mine I worked on a while ago where I created a similar module script and it does not have this issue.

It’s not recommended to constantly update DataStores like this, and instead you should simply keep this data in-memory on the server, and save it periodically (as well as when the player leaves) to the DataStore.

Another thing to point out, is that getmetatable is not a member of any old table value and has a signature of getmetatable(t: table), so you call it like so:

local t = setmetatable({}, {})
local mt = getmetatable(t)

And to address your original question, metatables are not retained when tables are passed as arguments to RemoteFunction:InvokeServer or RemoteFunction:InvokeClient. The receiving function on the other side will receive a table without a metatable.

1 Like

Thanks for the reply, literally just figured this out on my own right before reading this. I moved the remote function definitions to the same script containing the data tables and it works perfectly now, though it’s not ideal that I should have to have all of this in one script. Also, I’m not constantly updating the DataStore - I don’t save to the DataStore itself until the player leaves the game

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.