Jump to content

Getting an error even after using loadstring()


Dzsozi (h03)

Recommended Posts

Hello!

Lately I've been trying to make every script in OOP for simplicity. Since you have to define metatables and setup classes and methods for OOP to work properly, I tried to make an export function for it so I can create classes more easily. But when I was trying to do that and test it, I get an error message, which says:

spacer.png

And I don't really understand the reason why. What am I doing wrong? Right now I have 2 lua files, one of which contains the class creation, which is the following:

Class.lua (set to 'shared' type in meta)

function createClass()
	return [[
		local Class = {}
		Class.__index = Class
		setmetatable(Class, {__call = function( _,... ) return Class.new(...) end })

		function Class.new()
			local self = setmetatable({}, Class)
			return self
		end
	]]
end

and I have a test script, which is in the same resource, just a different lua file:

testscript.lua (set to 'server' type in meta)

loadstring(createClass())()

local testClass = Class()
testClass.name = "xd"

function testClass:getName()
	return self.name
end

print(testClass:getName())

As you can see I am using loadstring() function to infuse the class creation to the testscript.lua, but despite the fact I still get an error about Class() function being nil, why?

Also, I have the Class.lua (which is set as 'shared') before anything else in the meta, so I really don't understand what causes this error. Just in case if you were wondering, here is the meta.xml:

<meta>
	<oop>true</oop>
	
	<script src="Class.lua" type="shared"/>
	<script src="testscript.lua" type="server"/>
</meta>

Right now this is the full resource basically.

 

Thank you for your answer in advance!

Edited by Dzsozi (h03)
Link to comment
  • Dzsozi (h03) changed the title to Getting an error even after using loadstring()

UPDATE: I think I found the cause of the problem; if I make the Class table a global variable instead of a local variable, the script works, debugscript outputs 'xd' from testscript.lua as intended. But wouldn't the loadstring function make the script look basically like this?

--[[
	after using loadstring(createClass())() I imagine the script would have


	local Class = {}
	Class.__index = Class
	setmetatable(Class, {__call = function( _,... ) return Class.new(...) end })

	function Class.new()
		local self = setmetatable({}, Class)
		return self
	end


	at the beginning, after that comes the part down below
	
	|
	|
	V
]]


local testClass = Class()
testClass.name = "xd"

function testClass:getName()
	return self.name
end

print(testClass:getName())

 

So I don't understand why making the 'Class' table as a local variable would be a problem.

Is it because the type is defined as 'shared' in the meta, that's why I should make it a global variable??

UPDATE: I changed Class.lua type from 'shared' to 'server' to match testscript.lua type, made Class a local table again, and I got the error again, so I don't understand really why it should be a global variable.

 

Anyways, the fix was changing

function createClass()
	return [[
		local Class = {}
		Class.__index = Class
		setmetatable(Class, {__call = function( _,... ) return Class.new(...) end })

		function Class.new()
			local self = setmetatable({}, Class)
			return self
		end
	]]
end

to

function createClass()
	return [[
		Class = {}
		Class.__index = Class
		setmetatable(Class, {__call = function( _,... ) return Class.new(...) end })

		function Class.new()
			local self = setmetatable({}, Class)
			return self
		end
	]]
end

in case if you need this for your scripts.

I would still appreciate if somebody could answer my questions regarding that problem!

Edited by Dzsozi (h03)
Link to comment
  • Moderators
10 hours ago, Dzsozi (h03) said:

UPDATE: I changed Class.lua type from 'shared' to 'server' to match testscript.lua type, made Class a local table again, and I got the error again, so I don't understand really why it should be a global variable.

Do you remember that after loading a string, you have to call it again?

It basically means that there is a function around it. And every time you call that function, you run the code inside it.

-- https://www.lua.org/pil/8.html
f = loadstring("i = i + 1")

i = 0
f(); print(i)   --> 1
f(); print(i)   --> 2

See docs: https://www.lua.org/pil/8.html

 

So this is how it more or less looks like when loaded:

function ()
  Class = {}
  Class.__index = Class
  setmetatable(Class, {__call = function( _,... ) return Class.new(...) end })

  function Class.new()
      local self = setmetatable({}, Class)
      return self
  end
end

 

So what you want to do:

[[
local Class = {}
Class.__index = Class
setmetatable(Class, {__call = function( _,... ) return Class.new(...) end })

function Class.new()
	local self = setmetatable({}, Class)
	return self
end
return Class]]

 

local Class = loadstring(createClass())()

 

Link to comment
4 hours ago, IIYAMA said:

Do you remember that after loading a string, you have to call it again?

It basically means that there is a function around it. And every time you call that function, you run the code inside it.

 

That's how I did it in the first place, but I came up with an easier, and imo clenaer looking injector for lua files, this is how I did it:

ooplib/Class.lua (however, if I make Class = {} a local variable, I get Attempt to call global 'Class' nil value error, I am curious why?)

Class = {}
Class.__index = Class
setmetatable(Class, {__call = function( _,... ) return Class.new(...) end })

function Class.new(properties)
	local self = setmetatable(properties or {}, Class)
	return self
end

ooplib/infuse.lua

function getFileContent(filePath)
	if not filePath then return false end
	if not fileExists(filePath) then error("Can't open '"..filePath.."' (file doesn't exist)", 2) return false end
	
	local f = fileOpen(filePath, true)
	local content = f:read(f.size)
	f:close()
	
	return content
end

function classTemplate()
	return getFileContent("Class.lua")
end

otherresource/testscript.lua

loadstring(exports["sa_ooplib"]:classTemplate())() -- inject the Class.lua file's content

local Highlight = Class() -- call the Class function from the injected script
Highlight.__index = Highlight
setmetatable(Highlight, {__call = function(_,...) return Highlight:new(...) end}) -- making this so I can call Highlight()

function Highlight:new(element, style, color, flashSpeed, intensityOrThickness) -- create a Highlight class
	local data = setmetatable({}, {__index = self}) -- COULD BE A PROBLEM HERE? Change self to what? Or is it okay like this?
	-- set variables
	data.element = element
	-- and so on
	return data
end

-- testing
local playerHighlight = Highlight(localPlayer, "fresnel", {0,255,0,255}, 0, 1)
setTimer(function()
	local dumpster = Object(1337, localPlayer.position)
	playerHighlight:new(dumpster) -- [[ 	I DON'T WANT TO DO THIS :/    ]]
end, 2000, 1)

But as you look at the testscript.lua script, I commented a line which I have new problems with. I can call the :new method from the returned playerHighlight data. How can I avoid doing that?

Now I imagine that I would have to change the __index from self to something else?

local data = setmetatable({}, {__index = self})

Am I missing steps on setting up the Class.lua script?

Edited by Dzsozi (h03)
Link to comment

 UPDATE: I got rid of the loadstring()() injection concept for this Class.lua script because it looks unnecessary, I will just make classes independently in resources because it is almost the same effort to do right now for what I would like to achieve, so I just changed

local Highlight = {} --instead of Class()
-- Highlight:new to Highlight.new, and inside the .new function
local data = setmetatable({}, {__index = Highlight})

However, I can still call .new function in the testing section with

-- testing
local playerHighlight = Highlight(localPlayer, "fresnel", {0,255,0,255}, 0, 1) -- creating new Highlight class for player
local dumpster = Object(1337, localPlayer.position)
playerHighlight.new(dumpster) -- [[ 	I DON'T WANT TO DO THIS :/    ]]
  • Like 1
Link to comment
  • Moderators
16 hours ago, Dzsozi (h03) said:

However, I can still call .new function in the testing section with

 

Meta table = gets inherited

local Highlight = {test1 = true}

local newTable = setmetatable({test2 = true}, {__index = Highlight})

 

The normal table = is inheriting from meta table

local Highlight = {test1 = true}

local newTable = setmetatable({test2 = true}, {__index = Highlight})

 

print("newTable:", newTable.test1, newTable.test2 ) -- newTable:	true	true
print("Highlight:", Highlight.test1, Highlight.test2 ) -- Highlight:	true	nil

 

This means that the 'new' method should only be applied on newTable.

local Highlight = {test1 = true}

function newChild (self)
	print("self", self)
	return setmetatable({}, {__index = Highlight})
end

local newTable = setmetatable({test2 = true, new = newChild}, {__index = Highlight})

print(newTable:new())
print(newTable:new().test1) -- true
print(newTable:new():new()) -- attempt to call a nil value (method 'new')

 

 

 

 

 

 

 

Link to comment
On 18/02/2022 at 17:01, IIYAMA said:

 

Meta table = gets inherited

local Highlight = {test1 = true}

local newTable = setmetatable({test2 = true}, {__index = Highlight})

 

The normal table = is inheriting from meta table

local Highlight = {test1 = true}

local newTable = setmetatable({test2 = true}, {__index = Highlight})

 

print("newTable:", newTable.test1, newTable.test2 ) -- newTable:	true	true
print("Highlight:", Highlight.test1, Highlight.test2 ) -- Highlight:	true	nil

 

This means that the 'new' method should only be applied on newTable.

local Highlight = {test1 = true}

function newChild (self)
	print("self", self)
	return setmetatable({}, {__index = Highlight})
end

local newTable = setmetatable({test2 = true, new = newChild}, {__index = Highlight})

print(newTable:new())
print(newTable:new().test1) -- true
print(newTable:new():new()) -- attempt to call a nil value (method 'new')

For more explanation, please read the comments I made in the scripts as well;

I am sorry, but I don't really understand how should I change my code. Could you please provide me a solution for this?

So I have a Highlight table, each time this table gets called, a new Highlight is being created for the given element variable (I would like to make it function like the default MTA OOP stuff, like Vehicle(model, position) instead of Vehicle.new(model, position), so I have this setup here:

local CREATED_HIGHLIGHTS = {}

local Highlight = {}
Highlight.__index = Highlight
setmetatable(Highlight, {__call = function(_,...) return Highlight.new(...) end})

function Highlight.new(element, style, color, flashSpeed, intensityOrThickness)
	if not isElement(element) then return false end
	if CREATED_HIGHLIGHTS[element] then return false end
	
	local data = setmetatable({}, Highlight) -- this has something else to do with it I guess
  
  	-- set variables
  
	-- data.element = element
  
  	-- and so on

	return data
end

And I have methods for it like Highlight:update() and Highlight:destroy(), the way I did it for example:

function Highlight:destroy()
	if not CREATED_HIGHLIGHTS[self.element] then return false end
	if self.shader then self.shader:destroy() end	
	
	CREATED_HIGHLIGHTS[self.element] = nil
	
	return true
end

When I create a new highlight for the localPlayer, like:

local playerHighlight = Highlight(localPlayer, "outline", {255,0,0,255}, 0, 1)

Now everything works fine above, highlight gets created, I can access the data with playerHighlight.thickness and such, BUT

I can then use the playerHighlight table (which should contain only data, like style, thickness etc) to create a new highlight on a totally different element like:

local dumpster = Object(1337, localPlayer.position + Vector3(0,3,1))
print(playerHighlight.new(dumpster, "fresnel", {100,200,255,255}, 2, 0.5)) -- returns table, I don't want to use .new here
-- how can I make it return nil, or avoid calling of .new, but keep the :update() and :destroy() methods
-- I would like playerHighlight to contain only the data given before

Now I understand that the data table inherits from Highlight table at the Highlight.new function, so to the data table the methods get passed as well, but how should I change it so it won't inherit all the functions, I would like to access actual data and only methods with colon (:update, :destroy), not with dot (.new) . So my guess is that this line should be changed to something else instead of Highlight, I imagine

local data = setmetatable({}, Highlight) -- change Highlight to what? Am I missing the creation of another table?

 

Sorry if it looks like I am asking the same question twice, I am not ignoring your reply but I don't understand your explanation @IIYAMA, I mean I don't understand how should I implement it in this case.

Edited by Dzsozi (h03)
  • Like 1
Link to comment
  • Moderators
9 hours ago, Dzsozi (h03) said:

I mean I don't understand how should I implement it in this case.

You were almost there, you are doing great :)

The trick here is to split of the constructor. So that your new table will only inherit methods from the constructor.

local Highlight = {} -- base, just for the 'new' method
local HighlightConstructor = {} -- constructor

 

local CREATED_HIGHLIGHTS = {}

local Highlight = {} -- base, just for the 'new' method
local HighlightConstructor = {} -- constructor

function HighlightConstructor:destroy() iprint( self, 'destroy' ) end

--[[
	function HighlightConstructor:test() iprint( self ) end
]]


function Highlight:new( element, style, color, flashSpeed, intensityOrThickness )
	if not isElement( element ) then return false end
	if CREATED_HIGHLIGHTS[element] then return false end

	local data = { element = element, style = style, color = color, flashSpeed = flashSpeed, intensityOrThickness = intensityOrThickness }

	return setmetatable( data, { __index = HighlightConstructor } )
end

newHightlight = Highlight:new()
newHightlight:destroy()

 

Spoiler
function isElement() return true end

function iprint( ... ) return print( ... ) end

Some polyfill's for quick testing here:
https://www.lua.org/cgi-bin/demo

 

Note keep in mind there is a difference in . and : calls, depending on the set-up. Just in case you didn't know, which you probably did know.

test = {}

test.a = function (self) -- first parameter
  iprint(self)
end

test.a()

function test:b ()
  iprint(self) -- already available.
end

test:b()

 

  • Thanks 1
Link to comment
2 hours ago, IIYAMA said:

You were almost there, you are doing great :)

The trick here is to split of the constructor. So that your new table will only inherit methods from the constructor.

local Highlight = {} -- base, just for the 'new' method
local HighlightConstructor = {} -- constructor

  Finally, thank you so much!! This is what I was looking for, I just couldn't figure out how to implement it, I already knew in my mind that I will have to make 2 tables for this. For some reason I thought I can make the 'update' and 'destroy' functions in the same table as the 'new' function, turns out I can't if I want to avoid bad things to happen. :D

Now I have this setup at the beginning and it works fine as intended

local Highlight = {}
--Highlight.__index = Highlight    -- I DONT THINK I WILL NEED THIS ANYMORE
setmetatable(Highlight, {__call = function(_,...) return Highlight:new(...) end}) -- calling Highlight will call the Highlight:new method

local HighlightConstructor = {}
HighlightConstructor.__index = HighlightConstructor -- I NEED THIS INSTEAD

playerHighlight.style returns given style, playerHighlight.new or playerHighlight:new doesn't work like I wanted to.

2 hours ago, IIYAMA said:

Note keep in mind there is a difference in . and : calls, depending on the set-up. Just in case you didn't know, which you probably did know.

Yes, I knew that, thank you for giving a heads up! I changed it from . to : because you did it that way as well in your response, I am not sure tho what difference it makes in this case. I mean it should make no difference right here, if I am correct, since I am not using self inside the Highlight:new method.

Anyways thank you once again! I learned new stuff and I am happy for that, also I can finally finish this resource xd

 

EDIT: By the way, can I get rid of CREATED_HIGHLIGHTS and make it part of Highlight table? Will it work with a Highlight = {created = {}} setup or something like this? Or should I just keep them seperate? Since I was thinking about making methods like Highlight.getAllByElementType and such so I can get every created highlight of vehicles for example.

Edited by Dzsozi (h03)
  • Thanks 1
Link to comment
  • Moderators
41 minutes ago, Dzsozi (h03) said:

By the way, can I get rid of CREATED_HIGHLIGHTS and make it part of Highlight table? Will it work with a Highlight = {created = {}} setup or something like this? Or should I just keep them seperate? Since I was thinking about making methods like Highlight.getAllByElementType and such so I can get every created highlight of vehicles for example.

Yup, you can.

 

3 hours ago, IIYAMA said:
CREATED_HIGHLIGHTS = {}

local Highlight = {}
local Highlight 
local HighlightConstructor
do
  local register = {}
  -- depending were you want it.
  Highlight = {
      register = register
  } 

  HighlightConstructor = {
      register = register
  } 
end

function Highlight:new( element, style, color, flashSpeed, intensityOrThickness )
  
	local register = self.register
	if register[element] then return end

	local data = { element = element, style = style, color = color, flashSpeed = flashSpeed, intensityOrThickness = intensityOrThickness }
	
	register[element] = data
	-- ...
  
--
  
newHightlight = Highlight:new()
iprint(Highlight.register)
iprint(newHightlight.register)

 

 

  • Like 1
Link to comment
25 minutes ago, IIYAMA said:

Yup, you can.

 

local Highlight 
local HighlightConstructor
do
  local register = {}
  -- depending were you want it.
  Highlight = {
      register = register
  } 

  HighlightConstructor = {
      register = register
  } 
end

function Highlight:new( element, style, color, flashSpeed, intensityOrThickness )
  
	local register = self.register
	if register[element] then return end

	local data = { element = element, style = style, color = color, flashSpeed = flashSpeed, intensityOrThickness = intensityOrThickness }
	
	register[element] = data
	-- ...
  
--
  
newHightlight = Highlight:new()
iprint(Highlight.register)
iprint(newHightlight.register)

 

 

Thank you again! I will try to make and implement it! Really appreciate your time, have a nice day!

  • Like 1
Link to comment

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...