Jump to content

Recommended Posts

Posted (edited)

I'm pretty sure this is a bug, but I need a sanity check in case I'm misunderstanding something fundamental. I have a Class of type Target that I use to create class instances/objects that contain data about key strategic targets on the map. Each Target instance has an Event Handler that listens for an S_EVENT_DEAD Event and checks to see if the initiator is the DCS Object that it wraps (e.g. an EWR station, an AWACS, an ammo dump, etc.)

I created 7 of these Target class instances in a test mission, but I started getting a crash referring to a null event.initiator parameter. It turns out that the last instance to receive the Event does indeed receive an empty event.initiator parameter. My only thought is that the DCS object has been quietly cleaned up and destroyed by the time the last Target class instance receives the Event, but that seems really bizarre. Does anyone have any insights?

--- ON EVENT
-- Handle the death of the DCS Object associated with this Target.
-- @param self
-- @param event #table    The event table from DCS.
function AW.Target:OnEvent( event )
    
    if event.id == world.event.S_EVENT_DEAD then
    
        env.info( "AW MED: Event Initiator is type: " .. type(event.initiator))
        AW.Utils.OutputTableDebug( event )

<...snip...>

image.png

Edited by Pizzicato

i7-7700K @ 4.9Ghz | 16Gb DDR4 @ 3200Mhz | MSI Z270 Gaming M7 | MSI GeForce GTX 1080ti Gaming X | Win 10 Home | Thrustmaster Warthog | MFG Crosswind pedals | Oculus Rift S

Posted (edited)
3 hours ago, Pizzicato said:

I started getting a crash referring to a null event.initiator parameter.

I think you made two dangerous assumptions: one of them being that the data you receive has been validated before your callback is invoked. Unfortunately, you cannot rely on anything being the way you assume, logically derived or merely hope it is - in DCS, that will bite you. Silly as it seems, your two first lines in an event handler should be 

function milWings:onEvent(theEvent)
	if not theEvent then return end 
	if not theEvent.initiator then return end 

	<.. anything else ..>

Yeah, you should even check if there's an event record attached. Code quality and sanity in DCS has room to improve, especially when interfacing with Lua (for a fun time, pass a string as new flag value to trigger.action.setUserFlag() or nil as groupData to coalition.addGroup() - both hard crash DCS, something that simply should not be possible). So you are not insane, just too trusting 🙂 

Edited by cfrag
  • Like 1
Posted

Thanks cfrag.

I did actually add those checks anyway after posting this thread, but they make me a little uncomfortable in that it's now possible that I'll miss receiving a mission critical death event. I guess I can just run an occasional sanity check to make nothing's been missed, but it's a bit of an ugly workaround.

Thanks as ever for the response and the confirmation that I'm not going crazy. 🙂

i7-7700K @ 4.9Ghz | 16Gb DDR4 @ 3200Mhz | MSI Z270 Gaming M7 | MSI GeForce GTX 1080ti Gaming X | Win 10 Home | Thrustmaster Warthog | MFG Crosswind pedals | Oculus Rift S

Posted

For my 2p, I'll say that I've raised a bug about something related to this. 

On certain events (DEAD is an ideal one) the back end has DELETED the unit BEFORE it sends the event to the DCS Scripting Engine. 

When you try and do some introspection on the Unit to get it's ID or coalition, you get an error/crash because - yup - the object in question has been removed from the back end. 

Needless to say my bug report didn't get any traction as to fix all this you have to do a major change to the architecture for events and stuff. 

However, I was able to get around it by having my own copy of the object params in a table that I could refer to even if the object was gone. 

I'm not sure what you mean by classes and objects in LUA as LUA has no understanding of OOP, it's just tables and tables in a global table. If you are referring to a table as an 'object' then okay, but really that might muddy the waters (https://www.lua.org/pil/16.1.html#:~:text=Lua does not have the,languages%2C objects have no classes.)

Now, from what I'm understanding and correct me if I'm wrong, you have a unique table, with it's own event handler, for each of your seven 'target' aircraft right? 

Because parallelisation isn't a thing in LUA without additional libraries (Lua Lanes, Parallel Lua etc) then are you referring to coroutines? 

Either way, it's not like a C based language with true parallelisation.

Perhaps it might be cleaner if you have a table of your 'targets' and just one lua script that handles events, and you then iterate through the table of targets, passing in the event as part of a for loop. It means there is ONE SOURCE OF TRUTH (the event handler and the event) and it's much easier to debug.

Good luck. Sounds like you're making really good progress with all this. 

 

  • Like 2
Posted
2 hours ago, Pizzicato said:

they make me a little uncomfortable in that it's now possible that I'll miss receiving a mission critical death event.

Although that may indeed be possible, there is very little that you can do about it if the initiator is nil. At that point, it's too late to retrieve the object anyway. 

That being said, if the initiator isn't nil, you still don't always know if the object is still allocated. As you surmised, the backend can well deallocate the structure. In such a case, the unit/object will NOT be nilled (DCS uses pointers, not "handles", i.e. pointers to pointers), so the pointer will be stale. To protect against this, DCS provides guards that you can invoke on the "class" level (indeed, Lua's OOP is merely syntactic sugar, there is no "real" OOP in Lua, just an OOP-ish veneer, but for the purpose of DCS Lua let us pretend that Object is the root for Scenery and Coalition Objects, and Coa Object has been subclassed into Unit etc. Be VERY careful when invoking xxx.getCategory, as DCS's implementation for that method tree is a mess), for example

Unit.isExist(myUnit) -- when suspecting you have received a unit 

Group.isExist(myGroup) -- when you need to make sure a group reference hasn't gone stale

Hope this helps,

-ch

  • Like 1
Posted

Yep. That definitely helps. Thanks cfrag.

I did do a couple of night school courses in C++ about 6 or 7 years ago so I have a foundational understanding of OOP and its underlying principles, but that's about as far as it goes. I do know just enough to have a reasonable sense of the limitations and shortcomings of Lua's approximation of OOP, though.

I've definitely been caught out many times by the whole getCategory() thing - even over the last few days. I'm definitely finding myself having to script much more defensively in order to be 100% sure of the object type that I'm dealing with, but my checks are currently rather case-specific and hacky. I've started leaning into using more <Class>.isExist() checks recently, but not as rigorously or consistently as I should.  

If nothing else, DCS is slowly forcing me into more robust and considered scripting as I encounter more and more pitfalls and edge cases. 😀 

i7-7700K @ 4.9Ghz | 16Gb DDR4 @ 3200Mhz | MSI Z270 Gaming M7 | MSI GeForce GTX 1080ti Gaming X | Win 10 Home | Thrustmaster Warthog | MFG Crosswind pedals | Oculus Rift S

Posted (edited)
2 hours ago, TEMPEST.114 said:

For my 2p, I'll say that I've raised a bug about something related to this. 

On certain events (DEAD is an ideal one) the back end has DELETED the unit BEFORE it sends the event to the DCS Scripting Engine. 

When you try and do some introspection on the Unit to get it's ID or coalition, you get an error/crash because - yup - the object in question has been removed from the back end. 

Needless to say my bug report didn't get any traction as to fix all this you have to do a major change to the architecture for events and stuff. 

However, I was able to get around it by having my own copy of the object params in a table that I could refer to even if the object was gone. 

I'm not sure what you mean by classes and objects in LUA as LUA has no understanding of OOP, it's just tables and tables in a global table. If you are referring to a table as an 'object' then okay, but really that might muddy the waters (https://www.lua.org/pil/16.1.html#:~:text=Lua does not have the,languages%2C objects have no classes.)

Now, from what I'm understanding and correct me if I'm wrong, you have a unique table, with it's own event handler, for each of your seven 'target' aircraft right? 

Because parallelisation isn't a thing in LUA without additional libraries (Lua Lanes, Parallel Lua etc) then are you referring to coroutines? 

Either way, it's not like a C based language with true parallelisation.

Perhaps it might be cleaner if you have a table of your 'targets' and just one lua script that handles events, and you then iterate through the table of targets, passing in the event as part of a for loop. It means there is ONE SOURCE OF TRUTH (the event handler and the event) and it's much easier to debug.

Good luck. Sounds like you're making really good progress with all this. 

 

Hey @TEMPEST.114,

When I'm referring to OOP in Lua, I'm just referring to exactly the table-based "classes" that are described in the manual you linked to and here in the Lua-Wiki: http://lua-users.org/wiki/ObjectOrientationTutorial. The "class instances" that I'm creating are effectively just new tables that have metatables that fall back to the original class template (which is itself just a table). I think that's how DCS's Lua implementation functions, and also Moose's from when I tried deconstructing it for educational purposes a few years back. Again, though - I'm an enthusiastic amateur as opposed to a trained programmer.

Your suggestion about maintaining a single table of targets and a single Lua script that handles events makes a lot of sense, and I did start thinking about that after yesterday's issues. I'll have to think about what that means for the rest of my code, though. It's all fairly clean and nicely encapsulated, but it'd still probably be a little tricky. It may well be the smart approach, though.

The issue you raised about Unit tables being garbage collected before the event gets sent is infuriating, and I've had to fall back on the exact same approach of collating all of the Unit data in my own scripts as opposed to relying on DCS. It feels really hacky and inefficient, but it also seems like the only valid solution.

Thanks for taking the time to reply and provide suggestions and insights. 👍

Edited by Pizzicato
  • Thanks 1

i7-7700K @ 4.9Ghz | 16Gb DDR4 @ 3200Mhz | MSI Z270 Gaming M7 | MSI GeForce GTX 1080ti Gaming X | Win 10 Home | Thrustmaster Warthog | MFG Crosswind pedals | Oculus Rift S

Posted (edited)

I was getting this 'initiator' error -- see pic.

And fixed it with this line added to the code --

if(event.initiator ~= nil) then

For this --

-- detect if the Hosta bridge is destroyed:
-- Hosta Bridge Destroyed Flag is 91401
local mybridgedest = {}
function mybridgedest:onEvent(event)
 if (event.id == world.event.S_EVENT_DEAD) then
  if(event.initiator ~= nil) then -- Franze — 02/13/2024 3:44 PM
  if(event.initiator:getCategory() == 5 and event.initiator.id_ == 73695398) then
   trigger.action.setUserFlag(91401, true)
   trigger.action.outText("Hosta bridge destroyed!  Nice job, Blue.  Word", 10)
  end
  end
 end
end
world.addEventHandler(mybridgedest)

image.png

Edited by Wrecking Crew

Visit the Hollo Pointe DCS World server -- an open server with a variety of COOP & H2H missions including Combined Arms. All released missions are available for free download, modification and public hosting, from my Wrecking Crew Projects site.

Posted

Did you add a failsafe in case the missing event.initiator was the Hosta Bridge, and you'd therefore missed an important mission event?

i7-7700K @ 4.9Ghz | 16Gb DDR4 @ 3200Mhz | MSI Z270 Gaming M7 | MSI GeForce GTX 1080ti Gaming X | Win 10 Home | Thrustmaster Warthog | MFG Crosswind pedals | Oculus Rift S

Posted (edited)
1 hour ago, Wrecking Crew said:
if(event.initiator ~= nil) 

I recommend that you replace above with 

if event.initiator then

Your code can fail on the comparison because to compare the field with something, Lua has to access the field - which does not exist, forcing the error. The much simpler "if event.initiator then" merely checks if there is a field initiator, and if so, continues. If not, it skips without comparing it to anything.

 

Edited by cfrag
  • Like 1
Posted (edited)

cfrag - I will use your recommendation in my new mission.  My pal Franze has tried both methods, and said it's easy to test with an Explode the Bridge..  will do that if my errors continue.

Pizzicato - no idea why it's happening, and it is very random, maybe one in ever 20 runs of the mission, in both ME single player and on the server.

Edited by Wrecking Crew

Visit the Hollo Pointe DCS World server -- an open server with a variety of COOP & H2H missions including Combined Arms. All released missions are available for free download, modification and public hosting, from my Wrecking Crew Projects site.

Posted (edited)
6 hours ago, Wrecking Crew said:

will do that if my errors continue.

If you are modifying your code, you may also want to think about the following:

replace 

event.initiator:getCategory()

with 

Object.getCategory(event.initiator)

(see the notes here as to why this can be advantageous) and 

event.initiator.id_ == 73695398

with 

Object.getID(event.initiator)

Be advised that new releases of a map can easily change the ID of map objects, and you may want to switch to getting the initiator's Desc and home in on the returned desc.typeName. Unfortunately, that type name is not unique in DCS, so may have to add some positional information as well to ensure that you have the correct object. 

 

Edited by cfrag
Posted

Advise noted.. I am aware of the ID issue.  You and Franze talk the same language 😎  I'll work with him on my script and post it here, if that's OK with @Pizzicato to continue this conversation on this thread.

I had the same problem/solution detecting 'Roki Tunnel destroyed'.  Because, IIRC, Map Object Destroyed did not work.

Visit the Hollo Pointe DCS World server -- an open server with a variety of COOP & H2H missions including Combined Arms. All released missions are available for free download, modification and public hosting, from my Wrecking Crew Projects site.

Posted

@Wrecking Crew Feel free to keep adding to this thread. The more we can share the knowledge, the better for everyone. 👍

  • Like 1

i7-7700K @ 4.9Ghz | 16Gb DDR4 @ 3200Mhz | MSI Z270 Gaming M7 | MSI GeForce GTX 1080ti Gaming X | Win 10 Home | Thrustmaster Warthog | MFG Crosswind pedals | Oculus Rift S

Posted (edited)
On 5/13/2024 at 3:59 PM, Pizzicato said:

they make me a little uncomfortable in that it's now possible that I'll miss receiving a mission critical death event

You can always have a dedicated trigger for UNIT DEAD with an action of FLAG ON if you really want to be sure to trap that specific unit's death. That way, even if the back end deleted the unit before you get to do some introspection on the event via LUA, you can be assured you'll get the value of the flag as the ME triggers are processed every second whilst the units are still in the back end. 

When it comes to the Mission Editor / Scripting Engine, the 'belt and braces' approach wins out more often that I would like. 

Edited by TEMPEST.114
  • Like 2
Posted

This is what I ended up with, after testing combos of the recommendations.  

I kept the 'event.initiator.id_ == 73695398' bc without it there were errors

-- cfrag 04
-- detect if the Hosta bridge is destroyed:
-- Hosta Bridge Destroyed Flag is 91401
local mybridgedest = {}
function mybridgedest:onEvent(event)
 if (event.id == world.event.S_EVENT_DEAD) then
  if(event.initiator ~= nil) then -- Franze — 02/13/2024 3:44 PM
   -- if(event.initiator:getCategory() == 5 and event.initiator.id_ == 73695398) then
   if(Object.getCategory(event.initiator) == 5 and event.initiator.id_ == 73695398) then
    trigger.action.setUserFlag(91401, true)
    trigger.action.outText("Hosta bridge destroyed!  Nice job, Blue.  Word", 10)
   end
  end
 end
end
world.addEventHandler(mybridgedest)

--- This ^^^ works, no initiator errors.

 

--------------------------------------------------

My friend made some points to me that I want to share,,, this is his perspective… 

take a peek at the id field: it's looking for id_ because that object doesn't have an id field, it's got id_

image.png

 

so getID will return nil because the field id isn't there for this object... even though it should be, it just has an underscore instead

a number of static map objects are this way:

(This is the Hosta Bridge in the pic -- WC)

image.png

 

getID will work with most instances, but sometimes it's backward logic in DCS terms

`Object.getCategory(event.initiator)` vs `event.initiator:getCategory()` : depends on what category you're trying to find as these vary from type to type; using the latter saves some guesswork if you don't want to guess whether or not the initiator is an object or not

eg if you did Unit.getCategory you might get a nil result

for the record, within LUA, `if(variable ~= nil) then` is functionally identical to `if(variable) then` with the exception that the first will also detect a variable set to false

within LUA, everything undefined is nil

myarray = {flagone = "String", flagtwo = 2}

 

for i, d in pairs(myarray) do

trigger.action.outText("Key: " .. tostring(i) .. " Value: " .. tostring(d) .. "\n", 10)

end

 

if(myarray.flagthree ~= nil) then

trigger.action.outText("Oh no! Flag three has broken the space time continuum!", 10)

else

trigger.action.outText("Flag three is not present.", 10)

end

 

-- despite the key value flagthree not being present... ~= nil still works

 

 

 

 

Visit the Hollo Pointe DCS World server -- an open server with a variety of COOP & H2H missions including Combined Arms. All released missions are available for free download, modification and public hosting, from my Wrecking Crew Projects site.

Posted (edited)
On 5/17/2024 at 7:53 PM, Wrecking Crew said:

so getID will return nil because the field id isn't there for this object

This had me stumped, until I checked the API and I realized: The bridge is a Scenery object, and scenery objects do not have getID implemented (please see here)

The field 'ID_' that you are referencing is an API artifact of the Lua-->C bridge that luckily matches the ID of the object in-game.

Some more experimenting gave me the correct hint: you can use getName() to get the object ID. Silly? No, DCS! 

Also note that the ID that is assigned to map/scenery objects can change without notice between map releases.

Here's a brief demo miz I threw together, using your code, slightly modified to match any scenery object I intend to blow up 

local mybridgedest = {}
function mybridgedest:onEvent(event)
	if (event.id == world.event.S_EVENT_DEAD) and event.initiator then -- Franze — 02/13/2024 3:44 PM
		if(Object.getCategory(event.initiator) == 5) then  
			trigger.action.outText("scenery object destroyed!", 10)
			local mapObj = event.initiator
			local desc = mapObj:getDesc()
			if desc then 
				trigger.action.outText("killed '" .. desc.typeName .. "' (typeName)", 30)
			end 
			local name = mapObj:getName()
			if name then 
				trigger.action.outText("name:" .. name, 30)
			end 
		end
	end
end
world.addEventHandler(mybridgedest)

You'll receive two scenery object death events. Only the one for 72486987 is the one for the bridge (as you can see when you right-click on the bridge segment and look at the 'assign as...' trigger zone).

 

Test scenery dead event.miz

Edited by cfrag
  • Thanks 1
  • Recently Browsing   0 members

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