d0ppler Posted October 8 Posted October 8 are these two IF statements practically IDENTICAL, or will getsize() occur sooner/later than the other? if myAIGroup:getsize() < 1 then -- code here end if not myAIGroup then -- code here end A-10C, AV-8B, Ka-50, F-14B, F-16C, F-5E, F/A-18C, L-39, Mi-8, MiG-21, MiG-29, SA34, Spitfire, Su-27, Su-33, UH-1H
Sano Posted October 8 Posted October 8 (edited) The order of the calls depend on where they are added. In your above code, the first "if" will always be called before the second "if". They also are not entirely identical: if "myAIGroup" is not a table with a "getSize()" method, for example when it's nil, some random string or number, the if-check will throw an error. So again, depending on how and where it's used, it's generally safer to do: if not group or (group and group:getSize() == 0) then -- ... end This first checks if the group does not exist (so is dead), or if it still exists, it should not have units. There's also a small mistake in your snippet, getSize() should be with capital S. Edited October 8 by Sano 1
Tobias00723 Posted October 10 Posted October 10 (edited) Here is a snipped of my personal API due to the small nature of this here you go : this is the safest way to know a unit / group is safe to index and manipulate both code snippest above can still nil out but unlikely, with specifically "Attempt to perform arithmetic on '?' nil value" or something along the lines this can ac cure with empty tables and because a empty table is not nil the 'getSize()' will result in nil If a group/unit passes the "FULL" check the unit is Fully alive and active in the mission and is 'safe' to index the "limited" check gives 90% is good but also works on late activated units or units that may not exits YET (only difference is 'Object:isExist()') use as you wish you might need to edit the names/table structure because you guys dont have my "Utils.Unit" table ---@param Object? Object|Group|any ---@param Level? Safe_level ---@return boolean Utils.Unit.Safe_check = function ( Object, Level ) --[[ if Object and Object.id_ and Object:isExist() then return true end return false ]] local Level = Level or Utils.enum.Safe_Type.Full if Level == Utils.enum.Safe_Type.Full then if Object and type(Object) == "table" and Object.id_ and getmetatable(Object) and Object:isExist() then return true end elseif Level == Utils.enum.Safe_Type.Limited then if Object and type(Object) == "table" and Object.id_ and getmetatable(Object) then return true end end return false end Edited October 10 by Tobias00723 DCS Mission Scripting Wizard | TGFB Owner | Dev of HIP Dynamic Server | Maker of TGFB Dynamic | tgfb-dcs.com | Contact me | TGFB Discord
cfrag Posted October 10 Posted October 10 (edited) On 10/8/2025 at 2:01 PM, d0ppler said: are these two IF statements practically IDENTICAL, or will getsize() occur sooner/later than the other? Both are entirely different calls. They occur in the sequence that Lua encounters them in the script. Semantically you may think those statements to be similar, but in some very important cases they can be quite different -- so lets' see what they actually do On 10/8/2025 at 2:01 PM, d0ppler said: if myAIGroup:getsize() < 1 then This statement is pseudo-OOP, generated for the benefit of people writing code. It assumes quite a lot, and that may become a pitfall to those who do not regularly code. "myAIGroup:getSize()" does the following: it takes the contents of the variable myAIGroup, assumes that it is a reference to a table, accesses it's value as a table reference if the reference is stale (which can happen if the group is dead and was garbage-collected after you obtained the reference!), it is undefined what happens in Lua. If it is nil, Lua generates an error looks for a member named "getSize" in this table. If there is no such member, Lua generates an error assumes the member is of type function. If not, Lua generates an error executes the contents of member "getSize" as a function and returns the result. Contrast this to On 10/8/2025 at 2:01 PM, d0ppler said: if not myAIGroup then This code accesses the variable myAIGroup and uses Lua's internals to interpret myAIGroup as a variable of type boolean. If myAIGroup is not of type boolean then, if it is nil, this returns FALSE. In all other cases it returns TRUE (if myAIGroup is not of type Boolean and also not nil), or, if myAIGroup is of type boolean, it returns the boolean value. Semantically, above code wants to take advantage of Lua's duality for references - a nil reference means 'not defined' and everything else means 'is defined' - you are performing an implied nilcheck. The problem here - again - are stale handles. If you obtain myAIGroup immediately before you do the implied nilcheck 'if mygroup then', all is fine. However, if you obtain the reference earlier, and the group has died in the interim and was removed from memory ("garbage-collected"), you are in trouble. Lua does NOT garbage-collect external references (anything received from DCS API is external C code, definitely NOT Lua-integrated), so the memory location pointed to by myAIGroup has undefined values and may have been overwritten/repurposed. Your reference (pointer) to the table is stale, and your code is likely to take a dive off the deep end. You seem to assume that a group's reference is set to nil when a group dies. It is, but this does not propagate to the variable that you store a reference to that group in if you obtained the reference to a group while it was alive, and you retain that reference for use later. As a result, both your examples may contain serious bugs: you should obtain the reference to the group immediately before you dereference (i.e. access) it. If you do that, your code can be explicit or implicit, whatever floats your boat. I recommend you amend your code as follows: local myAIGroup = Group.getByName("myAIGroups cool name") -- get reference NOW! if not myAIGroup then return end -- nil reference, group does not exist any more, prob'ly dead or not yet spawned if myAIGroup:getsize() < 1 then -- group still exists but dead return end -- if we get here, myAIGroup is alive Now, if you want to detect that a group has died, use above code to detect if it is alive, and when it is alive, set a global variable/flag to remember that fact. And every time that the checks for dead fail, test if it was alive before, and only then you know that it once was alive and now is dead (i.e. transitioned from alive to dead -> died). Reset the alive flag, so this won't be flagged on subsequent passes or so that you can re-spawn. (Just passing through - hi all) Edited October 10 by cfrag 1
Tobias00723 Posted October 11 Posted October 11 Hi cfrag! Thanks for the VERRRY details detailed under the hood explaining! Interesting reading it ngl (Although think a bit overkill here xD) Recently (from what ive checked and remember) DCS added the "Unit:isAlive()" function that you can also use (Sadly not on a Group object) not sure this will result in the same logic but may be 'better' than the weird hacky way of using 'getsize()' Bonus time! I have the following function never really use it that ~should give the right output wanted here because i havent seen any documentation on 'unit:isAlive()' i put the 'should' here So @d0ppler have fun with this freebie ---if group is fully/partially alive or not ---@param group Group|nil ---@return boolean Utils.Unit.Is_Group_alive = function ( group ) if Utils.Unit.Safe_check(group) and group and group:getSize() > 0 then for _, unit in pairs(group:getUnits()) do if unit and Utils.Unit.Safe_check(unit) and unit:isAlive() then return true end end end return false end DCS Mission Scripting Wizard | TGFB Owner | Dev of HIP Dynamic Server | Maker of TGFB Dynamic | tgfb-dcs.com | Contact me | TGFB Discord
d0ppler Posted October 11 Author Posted October 11 (edited) Yeah, thanks for all your replies and help. This is my code, and I have 100% control over the bogey array (and bg_count), which only consists of airplanes. This is safe and does the job for my specific case: local function allBanditsDown() local b if trigger.misc.getUserFlag("NO_RED_IN_NORDIC") == 1 then return 1 else for i = 1, bg_count do b = Group.getByName(bogey[i]) if b then return 0 end end return 1 end end Edited October 11 by d0ppler A-10C, AV-8B, Ka-50, F-14B, F-16C, F-5E, F/A-18C, L-39, Mi-8, MiG-21, MiG-29, SA34, Spitfire, Su-27, Su-33, UH-1H
Recommended Posts