Jump to content

OOP vs element data


Recommended Posts

Hey everyone!

So there is something that I noticed while working in OOP-style with setElementData and getElementData.

Imagine you have something like this:

Race = {}
Race.__index = Race

function Race.new()
  local self = setmetatable({}, Race)
  self.title = 'Great 8-Track'
  self.laps = 10
  
  self.element = Element('race', 'Race 1')
  
  self.checkpoint = Marker(...)
  self.checkpoint:setParent(self.element)
  
  self.element:setData('sourceobj', self)
  
  return self
end

function Race:setTitle(string)
  self.title = string
end

function Race:getTitle()
  return self.title
end

function Race:getCheckpoint()
  return self.checkpoint
end

After pushing an instance of Race through some events (let's say from server -> client -> server), you'd like to change the title (i. e. you changed the title of the race through a GUI):

addEvent('races-updateTitle', true)
function updateTitle(raceObj, title)
  Race.setTitle(raceObj, title)
end
addEventHandler('races-updateTitle', resourceRoot, updateTitle)

Something strange I've noticed is that when updating with Race.setTitle(...), checking the title afterwards with Race.getTitle(...) shows my changes correctly, while checking the title like this:

addEvent('races-updateTitle', true)
function updateTitle(raceObj, title)
  Race.setTitle(raceObj, title)
  iprint(tostring(Race.getCheckpoint(raceObj):getParent():getData('sourceobj')))
end
addEventHandler('races-updateTitle', resourceRoot, updateTitle)

just shows the old state before changing the title.

 

Is this the way how OOP seem to behave or am I handling this stuff wrong? Because when simply using setElementData and getElementData on my Race element (not Object), everything works fine.

Thanks in advance!

Link to comment
22 hours ago, IIYAMA said:

setElementData on the race element, wouldn't change line 6, which sets a table value under the key "title".


self.title = 'Great 8-Track'

 

Is that not your issue?

 

setElementData doesn't change self.title.

I stored the Race object in its element. (see line 14 first code block)

However, when changing the title and grabbing the race object from Race element with getElementData again, nothing changes.

So basically, you can't edit a table's value when it's stored as elementData, right?

Edited by Sorata_Kanda
Link to comment
  • Moderators
1 minute ago, Sorata_Kanda said:

setElementData doesn't change self.title.

I stored the Race object in its element. (see line 14 first code block)

However, when changing the title and grabbing the race object from Race element with getElementData again, nothing changes.

So basically, you can't edit a table's value when it's stored as elementData, right?

yes,

 

Unless:

Link to comment
Just now, IIYAMA said:

yes,

 

Unless:

Hmm, I get a bad feeling when using OOP. It's not the same in lua as in e.g. Java. Maybe that's because first of Lua wasn't made for this and second of, you got client side and server side, which can't be able to share OOP objects.

Link to comment
  • Moderators
Just now, Sorata_Kanda said:

Hmm, I get a bad feeling when using OOP. It's not the same in lua as in e.g. Java. Maybe that's because first of Lua wasn't made for this and second of, you got client side and server side, which can't be able to share OOP objects.

Yes it is indeed different and limited to an extend.

I am using OOP for my code, but I am NOT using the MTA OOP classes which you might think is strange. The reason behind that is that I structure my code with OOP. The MTA OOP classes have nothing to do with with structuring the position of the code.

 

 

Link to comment
7 minutes ago, IIYAMA said:

Yes it is indeed different and limited to an extend.

I am using OOP for my code, but I am NOT using the MTA OOP classes which you might think is strange. The reason behind that is that I structure my code with OOP. The MTA OOP classes have nothing to do with with structuring the position of the code.

 

 

Do you define your classes the same way as I do?

I gave it a try to use MTA OOP classes and thus learned how to use OOP in Lua. It's sort of syntactic sugar, but OOP doesn't work the way I thought it would. Maybe because I store those objects in element data and some strange magic happens.

Edited by Sorata_Kanda
Link to comment
  • Moderators
6 minutes ago, Sorata_Kanda said:

Do you define your classes the same way as I do?

Nope, I write it a bit different. I am still experimenting with it to be honest and I haven't found my style yet.

 

See the client file of this resource:

 

It is looks like JavaScripts with a few Immediate Function Invocation hacks...

 

 

 

 

 

 

 

Link to comment
23 minutes ago, IIYAMA said:

Nope, I write it a bit different. I am still experimenting with it to be honest and I haven't found my style yet.

 

See the client file of this resource:

 

It is looks like JavaScripts with a few Immediate Function Invocation hacks...

 

 

 

 

 

 

 

Looks a bit complex to me but I'll take a deeper look later on.

My idea behind this Race class was to sort of make a Race element, which inherits the function of the element itself, and functions like setTitle can be used.

But inheritance in MTA OOP looks a bit messy.

Link to comment
  • Moderators
1 minute ago, Sorata_Kanda said:

Looks a bit complex to me but I'll take a deeper look later on.

My idea behind this Race class was to sort of make a Race element, which inherits the function of the element itself, and functions like setTitle can be used.

But inheritance in MTA OOP looks a bit messy.

We are doing more or less the same. The main difference is that I am building a higher object tree. Technically tables in tables.

The one of the race resource is just one layer.

 

Example.

clown = {
	animation = {
		sleep = function () end,
		dance = function () end
	},
	movement = {
		run = function () end,
		walk = function () end,
		fly = function () end
	},
	title = {
		set = function () end,
		get = function () end,
		remove = function () end
	}
}

 

A clown can do animations, movements and have a title. This example is used for single clown, without using the hidden variable self.

 

 

 

 

  • Like 1
Link to comment

@IIYAMA

Coming back to the problem I've stated above.

On 06/01/2019 at 17:31, Sorata_Kanda said:

Hey everyone!

So there is something that I noticed while working in OOP-style with setElementData and getElementData.

Imagine you have something like this:


Race = {}
Race.__index = Race

function Race.new()
  local self = setmetatable({}, Race)
  self.title = 'Great 8-Track'
  self.laps = 10
  
  self.element = Element('race', 'Race 1')
  
  self.checkpoint = Marker(...)
  self.checkpoint:setParent(self.element)
  
  self.element:setData('sourceobj', self)
  
  return self
end

function Race:setTitle(string)
  self.title = string
end

function Race:getTitle()
  return self.title
end

function Race:getCheckpoint()
  return self.checkpoint
end

After pushing an instance of Race through some events (let's say from server -> client -> server), you'd like to change the title (i. e. you changed the title of the race through a GUI):


addEvent('races-updateTitle', true)
function updateTitle(raceObj, title)
  Race.setTitle(raceObj, title)
end
addEventHandler('races-updateTitle', resourceRoot, updateTitle)

Something strange I've noticed is that when updating with Race.setTitle(...), checking the title afterwards with Race.getTitle(...) shows my changes correctly, while checking the title like this:


addEvent('races-updateTitle', true)
function updateTitle(raceObj, title)
  Race.setTitle(raceObj, title)
  iprint(tostring(Race.getCheckpoint(raceObj):getParent():getData('sourceobj')))
end
addEventHandler('races-updateTitle', resourceRoot, updateTitle)

just shows the old state before changing the title.

 

Is this the way how OOP seem to behave or am I handling this stuff wrong? Because when simply using setElementData and getElementData on my Race element (not Object), everything works fine.

Thanks in advance!

So basically, everytime I grab my Race object through getElementData(...) in my Race element and change the title, I need to update the object stored in my Race element again in order to complete the changes, right?

Edited by Sorata_Kanda
Link to comment
  • Moderators
3 minutes ago, Sorata_Kanda said:

@IIYAMA

Coming back to the problem I've stated above.

So basically, everytime I grab my Race object through getElementData(...) in my Race element and change the title, I need to update the object stored in my Race element again in order to complete the changes, right?

probably.

 

But can't you just make exports?

 

function Race:setTitle(string)
  self.title = string
end

-- export function
function raceSetTitle (string)
	return Race:setTitle(string)
end

 

Link to comment
8 hours ago, IIYAMA said:

probably.

 

But can't you just make exports?

 


function Race:setTitle(string)
  self.title = string
end

-- export function
function raceSetTitle (string)
	return Race:setTitle(string)
end

 

Well, as long as I don't need it in any other resouces, no.

Also:

local raceObj = Race.new(...)

-- sending object (table) through triggerClientEvent and triggerServerEvent

raceObj:setTitle(string) -- Won't work

Race.setTitle(raceObj, string) -- This works. Because Race:setTitle(string) equals Race.setTitle(self, string)

As I mentioned before, I think that sending OOP objects through events sort of messes up the table, setting functions defined in raceObj to nil.

Edited by Sorata_Kanda
Link to comment
  • Moderators
41 minutes ago, Sorata_Kanda said:

Well, as long as I don't need it in any other resouces, no.

Also:


local raceObj = Race.new(...)

-- sending object (table) through triggerClientEvent and triggerServerEvent

raceObj:setTitle(string) -- Won't work

Race.setTitle(raceObj, string) -- This works. Because Race:setTitle(string) equals Race.setTitle(self, string)

As I mentioned before, I think that sending OOP objects through events sort of messes up the table, setting functions defined in raceObj to nil.

Not being able to send functions is more than logic. Functions are bound to the blocks around it. But you could try to create a shared file with all classes in it. When sending a table over to the otherside the only thing left to do is putting the classes back in to the table.

 

Link to comment
10 hours ago, IIYAMA said:

Not being able to send functions is more than logic. Functions are bound to the blocks around it. But you could try to create a shared file with all classes in it. When sending a table over to the otherside the only thing left to do is putting the classes back in to the table.

 

So basically I have to create a new instance each time I send them over to the other side?

Link to comment
  • Moderators
1 hour ago, Sorata_Kanda said:

So basically I have to create a new instance each time I send them over to the other side?

Nope, not 100%, a new instance of the table is already happening behind the scenes.

 

A new instance will be created when: (there might be more)

  • When the table leaves the resource (export or event system)
  • When the table gets transferred from clientside <> serverside (event system)
  • When a table is send as parameter with timer (< not many people are aware of this one, but for timers there isn't a feature that is suppose to keep the table out of the garbage collector. So they let the timer make a deep-copy of the table.)

It might contain the same content, but it is not the same table.

 

 

Creating the instance is done already, but re-attaching the classes to give it the full functionality back is the remaining work.

 

 

 

 

 

 

 

 

 

 

Link to comment
23 hours ago, IIYAMA said:

Nope, not 100%, a new instance of the table is already happening behind the scenes.

 

A new instance will be created when: (there might be more)

  • When the table leaves the resource (export or event system)
  • When the table gets transferred from clientside <> serverside (event system)
  • When a table is send as parameter with timer (< not many people are aware of this one, but for timers there isn't a feature that is suppose to keep the table out of the garbage collector. So they let the timer make a deep-copy of the table.)

It might contain the same content, but it is not the same table.

 

 

Creating the instance is done already, but re-attaching the classes to give it the full functionality back is the remaining work.

 

 

 

 

 

 

 

 

 

 

Well, how do I do it?

Link to comment
  • Moderators
55 minutes ago, Sorata_Kanda said:

Well, how do I do it?

 

-- server
Race = {}
Race.__index = Race

addEventHandler("onResourceStart", resourceRoot, 
function ()
	attachRaceClasses(Race) -- initial
end)


addEvent("testRaceClasses", true)
addEventHandler("testRaceClasses", resourceRoot, 
function (raceElement) 
	attachRaceClasses(raceElement)
	outputChatBox(raceElement:getTitle())
end, false)


-- client
Race = {}
Race.__index = Race


addEventHandler("onClientResourceStart", resourceRoot, 
function ()
	attachRaceClasses(Race) -- initial
	
	local raceElement = Race.new()
	raceElement:setTitle("Sorata_Kanda")
	
	triggerServerEvent("testRaceClasses", resourceRoot, raceElement)
end)



-- shared
function attachRaceClasses (race)
	function race.new()
	  local self = setmetatable({}, race)
	  self.title = 'Great 8-Track'
	  self.laps = 10
	  
	  self.element = Element('race', 'Race 1')
	  
	  self.checkpoint = Marker(...)
	  self.checkpoint:setParent(self.element)
	  
	  self.element:setData('sourceobj', self)
	  
	  return self
	end

	function race:setTitle(string)
	  self.title = string
	end

	function race:getTitle()
	  return self.title
	end

	function race:getCheckpoint()
	  return self.checkpoint
	end
end

 

Like this. Nothing special.

 

Link to comment
  • Moderators
3 minutes ago, Sorata_Kanda said:

Ah, okay. Thank you.

Asking for a small piece of advice: Would you stick with oop in this case or rather use setelementdata to store title and stuff?

I am only using elementdata to share information globally or between resources(with sync disabled).

  • If I want to get the title for within the resource, then I will get it from a variable.
  • If I want to set the title within the resource, then I will save it in a variable.
    +
  • If I want to show the title of the map on a client his screen, yes I will use elementdata as second method.

 

 

Link to comment
10 minutes ago, IIYAMA said:

I am only using elementdata to share information globally or between resources(with sync disabled).

  • If I want to get the title for within the resource, then I will get it from a variable.
  • If I want to set the title within the resource, then I will save it in a variable.
    +
  • If I want to show the title of the map on a client his screen, yes I will use elementdata as second method.

 

 

Because you mentioned using elementdata non-synced: Would it still make sense to create an element called 'Race'?

I did that because I'd like to get later on all races by:

for i, race in ipairs(getElementsByType('race')) do
  -- do stuff here
end

Or is there another elegent solution?

Link to comment
  • Moderators
24 minutes ago, Sorata_Kanda said:

Because you mentioned using elementdata non-synced: Would it still make sense to create an element called 'Race'?

I did that because I'd like to get later on all races by:


for i, race in ipairs(getElementsByType('race')) do
  -- do stuff here
end

Or is there another elegant solution?

That would me sense.

But the way you are using it now is very heavy work. Searching through all elements in the whole server.

 

Would this not make more sense? (limiting to the resource only)

for i, race in ipairs(getElementsByType('race', resourceRoot)) do
  -- do stuff here
end

 

 

This is even faster and has a better structure:

-- server
local raceContainer = createElement("raceContainer", "raceContainer")
for i=1, 10 do
	setElementParent(createElement("race"), raceContainer)
end

 

(find one and access the rest)

-- client
local raceContainer = getElementByID("raceContainer")

local raceElements = getElementsByType("race", raceContainer) -- search also through sub-elements
-- or
local raceElements = getElementChildren(raceContainer, "race") -- just the direct children (faster)


for i, race in ipairs(raceElements) do
  -- do stuff here
end

 

Why bother making a race element?

- You can let sub-elements hang on it. They will be deleted when you delete the parent.

- Attach addEventHandlers to the parents of sub-elements. (which limits the event scope)

- Limit the scope of element queries. (like: getElementsByType)

- The benefit of having access to scoped propagation / inherits.

-- server
local raceContainer = createElement("raceContainer", "raceContainer")
for i=1, 10 do
	setElementParent(createElement("race"), raceContainer)
end

setElementDimension(raceContainer, 50)

(propagation / inherits is by default enabled: https://wiki.multitheftauto.com/wiki/SetElementCallPropagationEnabled)

 

 

 

 

 

  • Like 1
Link to comment
17 hours ago, IIYAMA said:

That would me sense.

But the way you are using it now is very heavy work. Searching through all elements in the whole server.

 

Would this not make more sense? (limiting to the resource only)


for i, race in ipairs(getElementsByType('race', resourceRoot)) do
  -- do stuff here
end

 

 

This is even faster and has a better structure:


-- server
local raceContainer = createElement("raceContainer", "raceContainer")
for i=1, 10 do
	setElementParent(createElement("race"), raceContainer)
end

 

(find one and access the rest)


-- client
local raceContainer = getElementByID("raceContainer")

local raceElements = getElementsByType("race", raceContainer) -- search also through sub-elements
-- or
local raceElements = getElementChildren(raceContainer, "race") -- just the direct children (faster)


for i, race in ipairs(raceElements) do
  -- do stuff here
end

 

Why bother making a race element?

- You can let sub-elements hang on it. They will be deleted when you delete the parent.

- Attach addEventHandlers to the parents of sub-elements. (which limits the event scope)

- Limit the scope of element queries. (like: getElementsByType)

- The benefit of having access to scoped propagation / inherits.


-- server
local raceContainer = createElement("raceContainer", "raceContainer")
for i=1, 10 do
	setElementParent(createElement("race"), raceContainer)
end

setElementDimension(raceContainer, 50)

(propagation / inherits is by default enabled: https://wiki.multitheftauto.com/wiki/SetElementCallPropagationEnabled)

 

 

 

 

 

Wow, I wish I'd come up with such a simple solution easily. Thank you very much :D

  • Like 1
Link to comment
  • Moderators
8 minutes ago, Sorata_Kanda said:

@IIYAMA

 

But in order to work with onClientRenders so I can display info with 3D Text labels, I need to store each information in elementData, right?

Yes,

 

elementData should be considered as something similar to attributes.

<race title="example" />

 

It defines the specifications of elements.

 

 

Before you continue, rendering based on elements is not the method with the most performance. So keep that in mind.

But! The benefits:

  • There is less (validation- and maintenance)code required. If an element is deleted, then you do not have to clean it from a table.
  • 100% control serverside.
  • Client elements can be set as children of server elements. So these can be auto cleaned as well.
  • The tables from the queries are formatted as arrays, so looping through them doesn't require much time.

 

 

 

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...