Page 1 of 1

a performant way of picking random entities

Posted: Wed Dec 02, 2020 2:32 am
by adamwong246
I'm making a mod that causes small, random damage to every entity on nauvis. Something like, every 1 minutes, pick 1 random entity from the entire map and cause 1% damage. But I'm having issues doing it performantly.

My (laggy) code ATM.

Code: Select all

function thingFallsApartMaybe()
	local nauvisSurface = game.get_surface("nauvis")
	local chunkIterator = nauvisSurface.get_chunks

	for chunk in chunkIterator() do
	  --log("x: " .. chunk.x .. ", y: " .. chunk.y)
	  --log("area: " .. serpent.line(chunk.area))

		local x = chunk.x + math.random(chunk.area.left_top.x, chunk.area.right_bottom.x)
		local y = chunk.y + math.random(chunk.area.left_top.y, chunk.area.right_bottom.y)

		local area = chunk.area
		local entities = nauvisSurface.find_entities(area)

		if entities then
			if next(entities) ~= nil then
			  log(entities[math.random(#entities)].name)
			end
		end
	end
end

script.on_nth_tick(60 * 1, thingFallsApartMaybe) --1 seconds

Is there a better way of doing this?

Re: a performant way of picking random entities

Posted: Wed Dec 02, 2020 7:50 am
by Choumiko
The biggest issue with your code right now is that you don't break/return after logging the random entity, causing it to go over every chunk in the map and log the name every tick.
I added a break after the log and comparing it to my version showed that is still maybe a little bit slower but it didn't lag on my save

Code: Select all

local function thingFallsApartMaybe2(e)
    local nauvisSurface = game.get_surface("nauvis")
    local get_random_chunk = nauvisSurface.get_random_chunk
    local find_entities = nauvisSurface.find_entities
    while true do --potential for infinite loop, if there are no entities on the map (for freeplay there should always be the character?)
        local chunk = get_random_chunk()
        local left_top = {x = chunk.x * 32, y = chunk.y * 32}
        local entities = find_entities({left_top = left_top, right_bottom = {x = left_top.x + 32, y = left_top.y + 32}})
        if next(entities) then
            local random_entity = entities[math.random(#entities)]
            --game.players[1].teleport(random_entity.position)
            --game.print(tries)
            log(random_entity.name)
            --do the damage
            return
        end
    end
end

Re: a performant way of picking random entities

Posted: Fri Dec 04, 2020 10:43 am
by eradicator
Choumiko wrote: Wed Dec 02, 2020 7:50 am

Code: Select all

    while true do --potential for infinite loop, if there are no entities on the map (for freeplay there should always be the character?)
        local chunk = get_random_chunk()
That looks like a seriously bad idea. You might be getting the same empty chunk over and over again, even on an only half-empty map. If @OP wants to guarantee that at least one entity is found i'd recommend shoving the coordinates of all known chunks into a table and randomly selecting from that, remembering which entires you've already tried during each pass. Or even remembering them permanently so that the randomness is equally distributed over the map. Using on_chunk_generated you also don't have to re-build the table every time.

But...statistically speaking you've got a really good chance to either hit a tree, biter or resource entity. So...what would even be the point? The effect on gameplay would literally be zero.

Re: a performant way of picking random entities

Posted: Fri Dec 04, 2020 4:19 pm
by adamwong246
I'd rather not access this data through a surface for that very reason. But I could not find anything in the api that could return "one random friendly entity with health", other than loading _every_ entity, then choosing a random one, but that can't possible be performant. So this led me to believe I could choose a random surface and then a random point on that surface and just let the RNG eventually hit a friendly entity.

The idea is to model a kind of random damage. Something like a lightening strike or a meteor impact.

Re: a performant way of picking random entities

Posted: Fri Dec 04, 2020 6:08 pm
by eradicator
Hm. I guess you could use the on_built/destroy_* based events to track what chunks on a surface do contain player-built entities at all. Then the selection is easier. I doubt that even the engine itself has a "list of all entities of this force" so there's no shortcuts. It's either try to keep track (error prone) or in each cycle probe around until you find something (costly).