Moderators Popular Post IIYAMA Posted March 30, 2018 Moderators Popular Post Share Posted March 30, 2018 Render events enhancement Having a lot of render events in your resource? Easier attach and remove? Or do you want to pass arguments in to a function which is attached to a render event? Then this might be something for you. Syntax: addRenderEvent bool addRenderEvent(function functionVar [, string eventName, arguments ...]) Arguments: The function you want to attach/target. The event you want to use. ( "onClientRender", "onClientPreRender", "onClientHUDRender") If you do not fill in one of these three, it will automatic fallback to "onClientRender". Fool proof. Arguments you can pass to the target function. (which isn't possible with the default addEventHandler + onClientRender function) Returns: true when added, and false otherwise. Syntax: removeRenderEvent bool removeRenderEvent(function functionVar [, string eventName]) Arguments: The function you want to attach/target. The event you want to use. ( "onClientRender", "onClientPreRender", "onClientHUDRender") If you do not fill in one of these three, it will automatic fallback to "onClientRender". Fool proof. Returns: true if found + removed, and false otherwise. (Not recommended to execute this function every frame > performance) onClientPreRender + timeslice If you use "onClientPreRender", just like the default event, it will pass the timeSlice to the attached/targetted function. https://wiki.multitheftauto.com/wiki/OnClientPreRender I am not sure if attached is the right word for this example, because it isn't really attached to the event. It is saved in a table which gets looped every frame. Performance Is this code bad for performance? The answer to that is NO. I ran a test for it, and it seems that addRenderEvent used less CPU AFTER adding the events. (addRenderEvent: 31% CPU, addEventHandler 99/100% CPU) Adding the event will probably use more CPU, but that is only one execution. Feel free to re-test this example, I am interested how it would perform on your pc's. Performance test code (Not the source code ) Spoiler addCommandHandler("runFirstTest", function () for i=1, 10000 do local testFunction = function () end addRenderEvent(testFunction) end end) addCommandHandler("runSecondTest", function () for i=1, 10000 do local testFunction = function () end addEventHandler("onClientRender", root, testFunction) end end) Source code: Spoiler ------------------------------ -- Authors: IIYAMA and Kenix -- ------------------------------ local addEventHandler = addEventHandler; local removeEventHandler = removeEventHandler; local table_remove = table.remove; local unpack = unpack; local len = table.getn; local root = root; local renderEvents = { "onClientRender", "onClientPreRender", "onClientHUDRender" } local allTargetFunctions = {} -- All attached functions will be stored in this table. local acceptedRenderEventTypes = {} -- Is type in use? See: renderEvents. local renderEventTypeStatus = {} -- Is the event in use? (so it doesn't have to be attached again) do -- prepare the data for i=1, len( renderEvents ) do local event = renderEvents[i] allTargetFunctions[event] = {} acceptedRenderEventTypes[event] = true renderEventTypeStatus[event] = false end end -- render all events here local function processTargetFunction ( timeSlice ) local targetFunctions = allTargetFunctions[ eventName ] local i = 1 local itemCount = len(targetFunctions) repeat if targetFunctions[ i ] == true then -- remove = true table_remove( targetFunctions, i ) itemCount = itemCount - 1 else local targetFunctionData = targetFunctions[i] if targetFunctionData then local arguments = targetFunctionData[2] if not arguments then targetFunctionData[ 1 ]( timeSlice ) else if timeSlice then targetFunctionData[ 1 ]( timeSlice, unpack( arguments ) ) else targetFunctionData[ 1 ]( unpack( arguments ) ) end end end i = i + 1 end until i > itemCount if len( targetFunctions ) == 0 then if renderEventTypeStatus[eventName] then removeEventHandler (eventName, root, processTargetFunction) renderEventTypeStatus[eventName] = false end end end -- check if a function is already attached function isRenderEventAdded (theFunction, event) if not event or not acceptedRenderEventTypes[event] then event = "onClientRender" end local targetFunctions = allTargetFunctions[event] for i = 1, len( targetFunctions ) do if targetFunctions[i] and targetFunctions[i] ~= true and targetFunctions[i][1] == theFunction then return true end end return false end local isRenderEventAdded = isRenderEventAdded -- add render event, default type is onClientRender function addRenderEvent(theFunction, event, ...) if not event or not acceptedRenderEventTypes[event] then event = "onClientRender" end if not isRenderEventAdded(theFunction, event) then local targetFunctions = allTargetFunctions[event] -- Don't pass an arguments if it not needed. local aArgs = { ... }; local mArgs = len( aArgs ) > 0 and aArgs or nil; targetFunctions[ len( targetFunctions ) + 1 ] = { theFunction, mArgs } -- attach an event if not renderEventTypeStatus[event] then addEventHandler (event, root, processTargetFunction, false, "high") renderEventTypeStatus[event] = true end return true end return false end -- remove a render event function removeRenderEvent(theFunction, event) if not event or not acceptedRenderEventTypes[event] then event = "onClientRender" end local targetFunctions = allTargetFunctions[event] for i = 1, len( targetFunctions ) do if targetFunctions[i] and targetFunctions[i] ~= true and targetFunctions[i][1] == theFunction then targetFunctions[i] = true -- true = remove return true end end return false end Updated by @Kenix Repository: https://github.com/Kenix157/mta_render_events Previous version: Spoiler -------------------- -- Author: IIYAMA -- -------------------- local renderEvents = { "onClientRender", "onClientPreRender", "onClientHUDRender" } local allTargetFunctions = {} -- All attached functions will be stored in this table. local acceptedRenderEventTypes = {} -- Is type in use? See: renderEvents. local renderEventTypeStatus = {} -- Is the event in use? (so it doesn't have to be attached again) do -- prepare the data for i=1, #renderEvents do local event = renderEvents[i] allTargetFunctions[event] = {} acceptedRenderEventTypes[event] = true renderEventTypeStatus[event] = false end end -- render all events here local processTargetFunction = function (timeSlice) local targetFunctions = allTargetFunctions[eventName] for i=#targetFunctions, 1, -1 do local targetFunctionData = targetFunctions[i] local arguments = targetFunctionData[2] if not arguments then targetFunctionData[1](timeSlice) else if timeSlice then targetFunctionData[1](timeSlice, unpack(arguments)) else targetFunctionData[1](unpack(arguments)) end end end end -- check if a function is already attached local checkIfFunctionIsTargetted = function (theFunction, event) if not event or not acceptedRenderEventTypes[event] then event = "onClientRender" end local targetFunctions = allTargetFunctions[event] for i=1, #targetFunctions do if targetFunctions[i][1] == theFunction then return true end end return false end -- add render event, default type is onClientRender function addRenderEvent(theFunction, event, ...) if not event or not acceptedRenderEventTypes[event] then event = "onClientRender" end if not checkIfFunctionIsTargetted(theFunction) then local targetFunctions = allTargetFunctions[event] targetFunctions[#targetFunctions + 1] = {theFunction, {...}} -- attach an event if not renderEventTypeStatus[event] then addEventHandler (event, root, processTargetFunction, false, "high") renderEventTypeStatus[event] = true end return true end return false end -- remove a render event function removeRenderEvent(theFunction, event) if not event or not acceptedRenderEventTypes[event] then event = "onClientRender" end local targetFunctions = allTargetFunctions[event] for i=1, #targetFunctions do if targetFunctions[i][1] == theFunction then table.remove(targetFunctions, i) if #targetFunctions == 0 then if renderEventTypeStatus[event] then removeEventHandler (event, root, processTargetFunction) renderEventTypeStatus[event] = false end end return true end end return false end 4 8 Link to comment
Saml1er Posted March 31, 2018 Share Posted March 31, 2018 Excellent. This is a very nice and clean implementation. I hope many people start using this. 1 Link to comment
Kenix Posted March 6, 2019 Share Posted March 6, 2019 (edited) You can also add this in top of the script, it should increase performance. local unpack = unpack; local len = table.getn; -- instead of #, you can't # make as local variable. Here is an updated version:https://github.com/Kenix157/mta_render_events Edited March 6, 2019 by Kenix 1 Link to comment
Moderators IIYAMA Posted March 8, 2019 Author Moderators Share Posted March 8, 2019 On 06/03/2019 at 22:00, Kenix said: You can also add this in top of the script, it should increase performance. local unpack = unpack; local len = table.getn; -- instead of #, you can't # make as local variable. Here is an updated version:https://github.com/Kenix157/mta_render_events Nice work! I updated the code in the tutorial. Link to comment
Scripting Moderators ds1-e Posted March 13, 2019 Scripting Moderators Share Posted March 13, 2019 Gonna try it soon 1 Link to comment
Moderators IIYAMA Posted May 16, 2019 Author Moderators Share Posted May 16, 2019 (edited) I noticed after applying Kenix his update that 1 of the loops wasn't inverted. Which is important for removing addRenderEvent's within the same process. -- for i = 1, len( targetFunctions ) do -- old for i = len( targetFunctions ), 1, -1 do Just like with domino, when you take a domino out of the line, it will not work well... Edited May 16, 2019 by IIYAMA Link to comment
Scripting Moderators ds1-e Posted May 19, 2019 Scripting Moderators Share Posted May 19, 2019 On 16/05/2019 at 22:17, IIYAMA said: I noticed after applying Kenix his update that 1 of the loops wasn't inverted. Which is important for removing addRenderEvent's within the same process. -- for i = 1, len( targetFunctions ) do -- old for i = len( targetFunctions ), 1, -1 do Just like with domino, when you take a domino out of the line, it will not work well... In removeRenderEvent function? 1 Link to comment
Moderators IIYAMA Posted May 19, 2019 Author Moderators Share Posted May 19, 2019 If the loop isn't invert, this will not work well: -- This will go wrong function theFunction () -- first function removeRenderEvent(theFunction) end addRenderEvent(theFunction) -- add multiple functions, else the context isn't valid for i=1, 9 do addRenderEvent(function () end) end functions: 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 Looping: from 1 t/m 10 | = index 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 Deleting 1 during the loop: | 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 Going on: | > > > > > > > > 2, 3, 4, 5, 6, 7, 8, 9, 10, nothing error: | 2, 3, 4, 5, 6, 7, 8, 9, 10, nothing If the loop is inverted, the loop will not have problems when items are removed. 1 Link to comment
Kenix Posted June 18, 2019 Share Posted June 18, 2019 (edited) On 19/05/2019 at 14:24, IIYAMA said: If the loop isn't invert, this will not work well: -- This will go wrongfunction theFunction () -- first function removeRenderEvent(theFunction)endaddRenderEvent(theFunction)-- add multiple functions, else the context isn't validfor i=1, 9 do addRenderEvent(function () end)end functions: 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 Looping: from 1 t/m 10 | = index 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 Deleting 1 during the loop: | 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 Going on: | > > > > > > > > 2, 3, 4, 5, 6, 7, 8, 9, 10, nothing error: | 2, 3, 4, 5, 6, 7, 8, 9, 10, nothing If the loop is inverted, the loop will not have problems when items are removed. @IIYAMA It cause another problem with render events, it starts call from end (which is not needed to us, because dx render events has "layers" and you need to call from 1 to table lenght) If you're using table.remove function, everything should work fine after remove item from table. Let me explain: local a = {}; local func = function() end; a[ #a + 1 ] = func; for i = 1, #a do if a[ i ] == func then table.remove( a, i ); end end print( #( a ) ); -- 0 table.remove safely remove item from table, it's not contains a [1] index with nil value, it's clear Proof: local a = {}; local func = function() end; a[ #a + 1 ] = func; for i = 1, #a do if a[ i ] == func then table.remove( a, i ); end end print( #( a ) ); -- 0 local func = function() end; a[ #a + 1 ] = func; print( #( a ) ); -- 1 Another example: local a = { 1, 2 }; local func = function() end; a[ #a + 1 ] = func; for i = 1, #a do if a[ i ] == func then table.remove( a, i ); end end print( #( a ) ); -- 2 local func = function() end; a[ #a + 1 ] = func; for i = 1, #a do if a[ i ] == func then table.remove( a, i ); end end print( #( a ) ); -- 2 And finally (shows how another items is reordered): from { [1] = 1, [2] = func, [3] = 2} -> {[1] = 1, [2] = 2} local func = function() end; local a = { 1, func, 2 }; print( "before", #a ); -- 3 for i = 1, #a do if a[ i ] == func then table.remove( a, i ); end end print( "after", #a ); -- 2 test here: https://www.Lua.org/cgi-bin/demo Edited June 18, 2019 by Kenix Link to comment
Moderators IIYAMA Posted June 18, 2019 Author Moderators Share Posted June 18, 2019 49 minutes ago, Kenix said: It cause another problem with render events, it starts call from end Agree, didn't thought about that one, but the counting up loop would still have the issue of skipping one(or more > context) of the items for that frame. The loop doesn't take into account that the index should stop increasing when an item is removed, as the array is collapsing. Would this not cover all the issues? local i = 1 repeat if a[ i ] == func then table.remove( a, i ) else i = i + 1 end until not a[ i ] Link to comment
Kenix Posted June 18, 2019 Share Posted June 18, 2019 3 hours ago, IIYAMA said: Agree, didn't thought about that one, but the counting up loop would still have the issue of skipping one(or more > context) of the items for that frame. The loop doesn't take into account that the index should stop increasing when an item is removed, as the array is collapsing. Would this not cover all the issues? local i = 1repeat if a[ i ] == func then table.remove( a, i ) else i = i + 1 enduntil not a[ i ] I think it's good solution. Code updated. Link to comment
Moderators IIYAMA Posted June 19, 2019 Author Moderators Share Posted June 19, 2019 7 hours ago, Kenix said: I think it's good solution. Code updated. It should be applied on the render function. The removeRenderEvent is fine as it was. The problem: <New frame> Queue start < fix should be applied here Func 1 removeRenderEvent (in func 1) Func 2 (error) Queue end Link to comment
Scripting Moderators ds1-e Posted July 22, 2019 Scripting Moderators Share Posted July 22, 2019 On 19/06/2019 at 12:30, IIYAMA said: It should be applied on the render function. The removeRenderEvent is fine as it was. The problem: <New frame> Queue start < fix should be applied here Func 1 removeRenderEvent (in func 1) Func 2 (error) Queue end This problem still exist? Link to comment
Moderators IIYAMA Posted July 22, 2019 Author Moderators Share Posted July 22, 2019 1 hour ago, majqq said: This problem still exist? Yes. I have a definitive solution in mind, but currently not the time to solve it. 1 Link to comment
Moderators IIYAMA Posted July 24, 2019 Author Moderators Share Posted July 24, 2019 (edited) @Kenix @majqq I think the issue is now solved. Had to make some annoying adjustments, before it finally did work in all the different context/scenario's. Now it doesn't matter when and where an item gets remove at any index. When it is remove, it gets temporary replaced by a boolean and removed while iterating/rendering. The render order is correct as well. I know it is a bit of a dirty hack, but it does not things up as it is in some of the known scenario's. Let me know if there are no new issues occurring. New version: (need some more testing, just in case) Spoiler ------------------------------ -- Authors: IIYAMA and Kenix -- ------------------------------ local addEventHandler = addEventHandler; local removeEventHandler = removeEventHandler; local table_remove = table.remove; local unpack = unpack; local len = table.getn; local root = root; local renderEvents = { "onClientRender", "onClientPreRender", "onClientHUDRender" } local allTargetFunctions = {} -- All attached functions will be stored in this table. local acceptedRenderEventTypes = {} -- Is type in use? See: renderEvents. local renderEventTypeStatus = {} -- Is the event in use? (so it doesn't have to be attached again) do -- prepare the data for i=1, len( renderEvents ) do local event = renderEvents[i] allTargetFunctions[event] = {} acceptedRenderEventTypes[event] = true renderEventTypeStatus[event] = false end end -- render all events here local processTargetFunction = function ( timeSlice ) local targetFunctions = allTargetFunctions[ eventName ] local i = 1 local itemCount = len(targetFunctions) repeat if targetFunctions[ i ] == true then -- remove = true table_remove( targetFunctions, i ) itemCount = itemCount - 1 else local targetFunctionData = targetFunctions[i] local arguments = targetFunctionData[2] if not arguments then targetFunctionData[ 1 ]( timeSlice ) else if timeSlice then targetFunctionData[ 1 ]( timeSlice, unpack( arguments ) ) else targetFunctionData[ 1 ]( unpack( arguments ) ) end end i = i + 1 end until i > itemCount end -- check if a function is already attached function isRenderEventAdded (theFunction, event) if not event or not acceptedRenderEventTypes[event] then event = "onClientRender" end local targetFunctions = allTargetFunctions[event] for i = 1, len( targetFunctions ) do if targetFunctions[i] and targetFunctions[i] ~= true and targetFunctions[i][1] == theFunction then return true end end return false end local isRenderEventAdded = isRenderEventAdded -- add render event, default type is onClientRender function addRenderEvent(theFunction, event, ...) if not event or not acceptedRenderEventTypes[event] then event = "onClientRender" end if not isRenderEventAdded(theFunction) then local targetFunctions = allTargetFunctions[event] -- Don't pass an arguments if it not needed. local aArgs = { ... }; local mArgs = len( aArgs ) > 0 and aArgs or nil; targetFunctions[ len( targetFunctions ) + 1 ] = { theFunction, mArgs } -- attach an event if not renderEventTypeStatus[event] then addEventHandler (event, root, processTargetFunction, false, "high") renderEventTypeStatus[event] = true end return true end return false end -- remove a render event function removeRenderEvent(theFunction, event) if not event or not acceptedRenderEventTypes[event] then event = "onClientRender" end local targetFunctions = allTargetFunctions[event] for i = 1, len( targetFunctions ) do if targetFunctions[i] and targetFunctions[i] ~= true and targetFunctions[i][1] == theFunction then targetFunctions[i] = true -- true = remove if len( targetFunctions ) == 0 then if renderEventTypeStatus[event] then removeEventHandler (event, root, processTargetFunction) renderEventTypeStatus[event] = false end end return true end end return false end Edited July 24, 2019 by IIYAMA 1 Link to comment
Scripting Moderators ds1-e Posted July 25, 2019 Scripting Moderators Share Posted July 25, 2019 (edited) 3 hours ago, IIYAMA said: @Kenix @majqq I think the issue is now solved. Had to make some annoying adjustments, before it finally did work in all the different context/scenario's. Now it doesn't matter when and where an item gets remove at any index. When it is remove, it gets temporary replaced by a boolean and removed while iterating/rendering. The render order is correct as well. I know it is a bit of a dirty hack, but it does not things up as it is in some of the known scenario's. Let me know if there are no new issues occurring. New version: (need some more testing, just in case) Reveal hidden contents -------------------------------- Authors: IIYAMA and Kenix --------------------------------local addEventHandler = addEventHandler;local removeEventHandler = removeEventHandler;local table_remove = table.remove;local unpack = unpack;local len = table.getn;local root = root;local renderEvents = { "onClientRender", "onClientPreRender", "onClientHUDRender"}local allTargetFunctions = {} -- All attached functions will be stored in this table.local acceptedRenderEventTypes = {} -- Is type in use? See: renderEvents.local renderEventTypeStatus = {} -- Is the event in use? (so it doesn't have to be attached again)do -- prepare the data for i=1, len( renderEvents ) do local event = renderEvents[i] allTargetFunctions[event] = {} acceptedRenderEventTypes[event] = true renderEventTypeStatus[event] = false endend-- render all events herelocal processTargetFunction = function ( timeSlice ) local targetFunctions = allTargetFunctions[ eventName ] local i = 1 local itemCount = len(targetFunctions) repeat if targetFunctions[ i ] == true then -- remove = true table_remove( targetFunctions, i ) itemCount = itemCount - 1 else local targetFunctionData = targetFunctions[i] local arguments = targetFunctionData[2] if not arguments then targetFunctionData[ 1 ]( timeSlice ) else if timeSlice then targetFunctionData[ 1 ]( timeSlice, unpack( arguments ) ) else targetFunctionData[ 1 ]( unpack( arguments ) ) end end i = i + 1 end until i > itemCountend-- check if a function is already attachedfunction isRenderEventAdded (theFunction, event) if not event or not acceptedRenderEventTypes[event] then event = "onClientRender" end local targetFunctions = allTargetFunctions[event] for i = 1, len( targetFunctions ) do if targetFunctions[i] and targetFunctions[i] ~= true and targetFunctions[i][1] == theFunction then return true end end return falseendlocal isRenderEventAdded = isRenderEventAdded-- add render event, default type is onClientRenderfunction addRenderEvent(theFunction, event, ...) if not event or not acceptedRenderEventTypes[event] then event = "onClientRender" end if not isRenderEventAdded(theFunction) then local targetFunctions = allTargetFunctions[event] -- Don't pass an arguments if it not needed. local aArgs = { ... }; local mArgs = len( aArgs ) > 0 and aArgs or nil; targetFunctions[ len( targetFunctions ) + 1 ] = { theFunction, mArgs } -- attach an event if not renderEventTypeStatus[event] then addEventHandler (event, root, processTargetFunction, false, "high") renderEventTypeStatus[event] = true end return true end return falseend-- remove a render eventfunction removeRenderEvent(theFunction, event) if not event or not acceptedRenderEventTypes[event] then event = "onClientRender" end local targetFunctions = allTargetFunctions[event] for i = 1, len( targetFunctions ) do if targetFunctions[i] and targetFunctions[i] ~= true and targetFunctions[i][1] == theFunction then targetFunctions[i] = true -- true = remove if len( targetFunctions ) == 0 then if renderEventTypeStatus[event] then removeEventHandler (event, root, processTargetFunction) renderEventTypeStatus[event] = false end end return true end end return falseend Thanks, i will let you know, if something will not work. Besides, what do does in this case? Edited July 25, 2019 by majqq Link to comment
Moderators IIYAMA Posted July 25, 2019 Author Moderators Share Posted July 25, 2019 4 hours ago, majqq said: Thanks, i will let you know, if something will not work. Besides, what do does in this case? It is just an extra block, which runs 1 time. Similar to: if true then end And no it is not required, but it makes it very clear that some action are happening there. You more often see those be used to enclose reused variables, without overwrite the original. local base = 1 if 1 == base then do local base = 2 print(base) -- 2 end do local base = 3 print(base) -- 3 end print(base) -- 1 end Or delete unused variables: local a do local b = 100 a = b * b * b end 1 1 Link to comment
Moderators IIYAMA Posted August 1, 2019 Author Moderators Share Posted August 1, 2019 Updated the topic to latest version. P.s. if you are interested in always receive notifications when the newest version comes out. There is a feature on the forum called: following. This allows you to keep track of updates from a selected topic. It is located on the right + top corner of where the topic starts. 1 Link to comment
Einheit-101 Posted August 14, 2019 Share Posted August 14, 2019 I find it ridiculous that this isnt a basic thing of Lua itself, it should not eat less performance when functions are stored as local variables... Link to comment
Moderators IIYAMA Posted August 20, 2019 Author Moderators Share Posted August 20, 2019 On 14/08/2019 at 23:27, Einheit-101 said: I find it ridiculous that this isnt a basic thing of Lua itself, it should not eat less performance when functions are stored as local variables... It is indeed a little bit strange. But on the other side, it does provide a way to optimise access for variables manually. It is just a pity that it can't optimise itself based on patterns of variable requests. Link to comment
Einheit-101 Posted August 24, 2019 Share Posted August 24, 2019 I would suggest a "setting" that automatically stores all Lua functions inside the computers RAM for people who can afford that, it would increase performance of large scripts tremendously. Link to comment
roaddog Posted May 6, 2022 Share Posted May 6, 2022 This is nice way to organize, gonna implement this and see how it goes Link to comment
Recommended Posts