mapArea iterator

Discussion in 'Mods & Lua Functions' started by Colandus, Jan 6, 2017.

  1. Colandus

    Colandus Support Team Support Team

    Joined:
    Jun 6, 2007
    Messages:
    2,414
    Likes Received:
    148
    Best Answers:
    17
    Hey. This is a revised version of my old mapArea iterator. This simplifies looping through areas.

    My old iterator was a bit more confusing for developers who might have wanted to edit the iterator function itself. This time it's using coroutines so the function should be very easily customizable.

    The old iterator was using stackpos, this one isn't. I assume it's irrelevant these days? It also fetched the creature on the tile however, which this one doesn't. But I plan to add it. I wanted some input from you guys how I should approach this. Just return top thing as second value or should we have an option to get e.g. TOP_CREATURE or TOP_ITEM or an option to allow it to loop through each item on every tile (without the necessity of a second loop)? Or a callback? Possibility to reverse loop?

    I feel like this function never was much appreciated back when I made this, which confuses me. I fail to understand why anyone would like to create 3 loops in place of a single one, which also might save you a few more lines checking for a player etc...

    Enough talk, here's the code, place this wherever you place your functions normally:
    Code (Text):
    1.  
    2. do
    3.     local function mapAreaGen(fromPos, toPos)
    4.         for x = fromPos.x, toPos.x do
    5.             for y = fromPos.y, toPos.y do
    6.                 for z = fromPos.z, toPos.z do
    7.                     coroutine.yield(Position(x, y, z))
    8.                 end
    9.             end
    10.         end
    11.     end
    12.  
    13.     function mapArea(fromPos, toPos)
    14.        local co = coroutine.create(function () mapAreaGen(fromPos, toPos) end)
    15.        return function()
    16.            local _, pos = coroutine.resume(co)
    17.            return pos
    18.        end
    19.     end
    20. end
    21.  
    Simple example usage:
    Code (Text):
    1.  
    2. local fromPos = Position(...)
    3. local toPos = Position(...)
    4. for position in mapArea(fromPos, toPos) do
    5.    -- position:sendMagicEffect(CONST_ME_MAGIC_BLUE)
    6. end
    7.  
    COMPARED to current way of looping:
    Code (Text):
    1.  
    2. local fromPos = Position(...)
    3. local toPos = Position(...)
    4. for x = fromPos.x, toPos.x do
    5.    for y = fromPos.y, toPos.y do
    6.       for z = fromPos.z, toPos.z do
    7.          local position = Position(x, y, z)
    8.          -- position:sendMagicEffect(CONST_ME_MAGIC_BLUE)
    9.       end
    10.    end
    11. end
    12.  

    If you are intending to use this function, don't forget to come back and look for updates in short. In the meantime, I would like some input on how I should be adding the thing, creature etc...
     
    abobakrwaheed74 and Tony32 like this.
  2. Xeraphus

    Xeraphus Support Team Support Team Premium User

    Joined:
    Feb 14, 2015
    Messages:
    2,087
    Likes Received:
    784
    Best Answers:
    65
    using coroutines for no reason isnt exactly more efficient
    yes it works but it's not needed
    if you compare the times of your function vs the old loop in a 1000x1000x1 area your function is almost twice as slow

    if you still wanted to create a function for this instead of regular looping you could pass a function through mapArea like:
    mapArea(fromPos, toPos, func, ...)
    that way the user can define what exactly they want to do with their own function

    Code (Text):
    1.  
    2. min, max = math.min, math.max
    3.  
    4. function mapArea(fromPos, toPos, func, ...)
    5.     for z = min(fromPos.z, toPos.z), max(fromPos.z, toPos.z) do
    6.         for x = min(fromPos.x, toPos.x), max(fromPos.x, toPos.x) do
    7.             for y = min(fromPos.y, toPos.y), max(fromPos.y, toPos.y) do
    8.                 local position = Position(x, y, z)
    9.                 func(position, ...)
    10.             end
    11.         end
    12.     end
    13.     return true
    14. end
    15.  
    16. local function cleanPosItemsById(position, itemid)
    17.     local tile = Tile(position)
    18.     if not tile then
    19.         return false
    20.     end
    21.     local tileItem
    22.     for index = 0, tile:getItemCount() do
    23.         tileItem = tile:getThing(index)
    24.         if (tileItem:getId() == itemid) then
    25.             tileItem:remove()
    26.         end
    27.     end
    28.     return true
    29. end
    30.  
    31. mapArea(Position(994, 997, 7), Position(1007, 1004, 7), cleanPosItemsById, 2160)
     
    Last edited: Jan 6, 2017
    Colandus likes this.
  3. 496815

    496815 New Member

    Joined:
    Jul 13, 2010
    Messages:
    19
    Likes Received:
    5
    Best Answers:
    0
    @Colandus I actually did appreciate your old function and have used it a lot, I did modify it a bit though.

    Regarding monsters / items: I'd just make new functions for those to be honest, seems messy enough as it is.
     
    Colandus likes this.
  4. Colandus

    Colandus Support Team Support Team

    Joined:
    Jun 6, 2007
    Messages:
    2,414
    Likes Received:
    148
    Best Answers:
    17
    @Xeraphus Twice as slow isn't much, talking microseconds. Coroutines for no reason? Coroutines aren't slow though and there is a good reason; code is much cleaner, if you had seen the old mapArea iterator. But if you want to compare only with the regular for loops, you gain cleaner code and easier usability in exchange for an insignificant increase of workload.

    While your example isn't bad, I'm not a fan of using callbacks too much. Just a personal preference, hence why I tend to make these types of iterators. I want to use the loop without the need of nesting 3 loops each time. Saving lines I guess, not just for the result but for the time it takes typing them. By also returning a creature or item or similar I would save more lines than just writing less loops. Also this is directed to scripters of all levels, and most of them get confused by callbacks, not to mention a bunch of nested loops. Frankly I rarely need to use this iterator myself. But I suppose that's because I rarely need to scan areas as I use other better methods for whatever it is I'm going to do.

    Also that upvalue of tileItem is redundant and also slower than adding it directly inside the loop. As well recalculating the min and max on each iteration is slow.

    @496815 Yeah, I just feel it's more convenient if you'd like to look for e.g. players only, you won't need to add an extra variable (local player = Tile(pos):getTopVisibleCreature(), or at worst loop getCreatures) and an additional if-statement. For now I'm considering using flags or perhaps as bot scripts tend to have it a string with e.g. "pmn" to select player, monster, npc. Not sure yet.

    My main usage of such functions is actually using live in-game codes. You want them as short as possible. I tend to write lots of in-game codes each day.
     
  5. Xeraphus

    Xeraphus Support Team Support Team Premium User

    Joined:
    Feb 14, 2015
    Messages:
    2,087
    Likes Received:
    784
    Best Answers:
    65
    i know twice as slow isn't much when we're talking about a small area, but you're releasing code for the community.
    you should make it efficient and fool-proof because you don't know what people are going to do with it + make it customizable to the user's preference rather than your preference

    callbacks aren't exactly a very hard thing to understand, a good example of using a callback is in addEvent, which evan has a tutorial for in the tutorials section.
     
  6. Colandus

    Colandus Support Team Support Team

    Joined:
    Jun 6, 2007
    Messages:
    2,414
    Likes Received:
    148
    Best Answers:
    17
    Sometimes readability and convenience is to be considered before performance if the difference isn't significant. Obviously I tend to make it customizable, perhaps you didn't read my post and just skipped to the code. And as you said, it's about giving the users options right? Well, anyone can make a function that takes a callback. At least I'd imagine anyone who can use a callback can write such a function themselves. And if they cannot, well you have provided them with one right? So that's good. Now they have options. But those who prefer to use loops, they may use this iterator. If loops weren't necessary, ipairs would be a callback and all other iterators and loops in general. Loops are sometimes convenient.
     
  7. Colandus

    Colandus Support Team Support Team

    Joined:
    Jun 6, 2007
    Messages:
    2,414
    Likes Received:
    148
    Best Answers:
    17
    I benchmarked and yours is ~35% slower even when removing the math.min/max calls. Coroutines are faster in Lua, functions are slow.


    Compared to my old mapArea without coroutines (but more complex code), the new one using coroutines is only about 3,5% slower. Here is what it looks like:
    Code (Lua):
    1.  
    2. function mapArea(fromPos, toPos)
    3.     local x, y, z = fromPos.x, fromPos.y-1, fromPos.z
    4.     return function()
    5.         if (y < toPos.y) then
    6.             y = y+1
    7.         elseif (x < toPos.x) then
    8.             y = fromPos.y
    9.             x = x+1
    10.         elseif (x == toPos.x and y == toPos.y and z < toPos.z) then
    11.             x = fromPos.x
    12.             y = fromPos.y
    13.             z = z+1
    14.         else
    15.             x = x + 1
    16.         end
    17.         if (x <= toPos.x and y <= toPos.y or z < toPos.z) then
    18.             return Position(x, y, z)
    19.         end
    20.     end
    21. end
    22.  
    So if anyone would care about such insignificant performance loss for the cost of less readable code, then they'd be free to use this version. Obviously, if someone preferred to use callbacks they may do so too. Although, it should not be about performance but rather coding style and preference.
     
    Last edited: Feb 14, 2017

Share This Page

Loading...