Dzsozi (h03) Posted February 17, 2022 Share Posted February 17, 2022 (edited) 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: 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 February 17, 2022 by Dzsozi (h03) Link to comment
Dzsozi (h03) Posted February 17, 2022 Author Share Posted February 17, 2022 (edited) 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 February 17, 2022 by Dzsozi (h03) Link to comment
Moderators IIYAMA Posted February 17, 2022 Moderators Share Posted February 17, 2022 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
Dzsozi (h03) Posted February 17, 2022 Author Share Posted February 17, 2022 (edited) 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 February 17, 2022 by Dzsozi (h03) Link to comment
Dzsozi (h03) Posted February 17, 2022 Author Share Posted February 17, 2022 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 :/ ]] 1 Link to comment
Moderators IIYAMA Posted February 18, 2022 Moderators Share Posted February 18, 2022 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
Dzsozi (h03) Posted February 20, 2022 Author Share Posted February 20, 2022 (edited) 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 February 20, 2022 by Dzsozi (h03) 1 Link to comment
Moderators IIYAMA Posted February 20, 2022 Moderators Share Posted February 20, 2022 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() 1 Link to comment
Dzsozi (h03) Posted February 20, 2022 Author Share Posted February 20, 2022 (edited) 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. 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 February 20, 2022 by Dzsozi (h03) 1 Link to comment
Moderators IIYAMA Posted February 20, 2022 Moderators Share Posted February 20, 2022 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) 1 Link to comment
Dzsozi (h03) Posted February 20, 2022 Author Share Posted February 20, 2022 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! 1 Link to comment
Recommended Posts
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 accountSign in
Already have an account? Sign in here.
Sign In Now