Page 2 of 6 FirstFirst 1 2 3 4 ... LastLast
Results 11 to 20 of 57

Thread: SUMMARY OF ISSUES

  1. #11
    Basic Member
    Join Date
    Dec 2016
    Posts
    180
    Yup you are right, sorry about that. I meant some sort of local tables.
    recently i tried to reduce number of global variables in my code and I saw the results u already mentioned. thanks for that

  2. #12
    Basic Member
    Join Date
    Sep 2017
    Posts
    56
    Why are global functions/variables so especially bad in lua? And what do you mean with globals exactly? Aren't all variables and functions just things inside of tables?



    And yeah I've got some infinite loop requires. It doesn't seem to be a problem if there aren't any parse errors though.

    Further finds:
    - Bots will continue to say "Good luck have fun" even with a full bot override
    - Bots will continue to announce things like "missing mid" in a full bot override

    - There is some kind of issue happening with the location passability grid. Many spots are shown as passable when they're not, and others are shown as impassable when they're not. This seems to affect both GeneratePath() and IsLocationPassable()
    For example:
    https://imgur.com/a/lZpeV I'm standing on a spot marked impassable, while the spot above me is marked passable yet I can't move there
    https://imgur.com/a/mdCFR There is no spot marked impassable for this tree, while it's obstructing movement

    Even worse, besides spots being marked the wrong way, there appears to be an issue where the grid doesn't align well what is actually happening.
    If I draw circles around slardar showing where Slardar thinks he can stand based on IsLocationPassable, testing spots by just offsetting based on his position we get this:
    https://puu.sh/xPMQx/0d0b5520c3.png
    This doesn't align at all with the grid from dota_bot_debug_grid 8
    Even if the misalignment of grids is visual in nature and based on camera angle or something, the IsLocationPassable result is functional. Slardar currently thinks he can't move to the left because the spot to the left of him (after taking is location and subtraction in the x direction) is marked impassable. This despite the trees being way far to the left
    This happens around the map to varying degrees:
    https://puu.sh/xPLFl/55c09c4b0a.png
    I'm thinking it may be some kind of off by one error, where something in the underlying system thinks the map is one tile smaller than it is, and the difference is smeared out over the map. Resulting in a further offset on the right side of the map

    It's the cause of a lot of pathing issues for me. Sometimes they think they can't move somewhere and won't, sometimes they think they can move somewhere but the game doesn't allow it
    Last edited by Siesta Guru; 10-04-2017 at 05:50 AM.

  3. #13
    Basic Member
    Join Date
    Mar 2012
    Posts
    2,014
    Quote Originally Posted by Siesta Guru View Post
    Why are global functions/variables so especially bad in lua? And what do you mean with globals exactly?
    Global "stuff" resides in the global table called _G.
    You can declare a function and assign it as a member to a table like so:
    Code:
    local Stuff
    function Stuff.MyFunc ()
        -- do stuff
    end
    return Stuff;
    And then use it like this:
    Code:
    local StuffModule = require("stuff");
    And call it like this:
    Code:
    StuffModule.MyFunc();
    Nothing new, right?
    What some don't know is about the non-standard way to call a function. You can also do this
    Code:
    StuffModule["MyFunc"]();
    The first thing you're prolly thinking of is "heeeey, wait a minute... I can access indexed lists like this MyTable[4], but I know I can access table members like this MyTable.SomeVar or MyTable["SomeVar"] ... are you saying I can call functions the same way?"
    Yes! Lua is built around tables. Everything is about tables. Except for local variables (and other stuff, but let's simplify it).
    Locally defining stuff in a block, for example in a function, it all gets saved in the stack. Simply put, if the VM is smart enough, it can place part of the data from the stack frame in registers. But even so, anything on the stack is faster, it's all indexed via pointers. For example, when you call a function with variables as params, or call it with hardcoded values as params (what I mean is: v_thing = "Jump"; DoThis(v_thing) or DoThis("Jump")), no variable is passed. Nothing is. It will pass the actual values. So when calling DoThis, only "Jump" is sent. This discussion will go into a more detailed area if we're going to talk about PODs and objects, passing by value and by reference, pointers and constants (because there is a difference between calling a function with numbers and passing a table as parameter), but the idea is, when working on the stack, the VM might even "localize" received parameters just for efficiency. Obviously when I say "parameters" I mean the actual values.

    You have to understand. Realistically, Lua doesn't know the names of anything. It doesn't know what param name it has, the function name, the type, anything, until it actually uses it. Running a strict debug session using the Lua debug library will show you that there are moments when it can't even identify function names UNLESS they were explicitly called from Lua. C calls are another story. And truth is, it doesn't need to know. The whole reason why it's so efficient is because Lua is working with data, not with names, variables or functions. I guess from a design point of view, it's one of the most clever scripting approaches I've seen.

    Globals now, are another story. This is because they are stored inside a table. While tables are fast, they aren't as fast as pointers. Pointers are fast because as their name implies, they point to something. They are an address and you know that when you access that pointer (that address) then it returns what is there. There is no "processing" or "searching". It's like an index, like an open directory. Page 6, paragraph 7. Instant hit, similar to searching something at a specific coordinate.
    Tables on the other hand, are of 2 types: indexed (the arrays, which have sequential numbers as keys and store the data in values) and hashed (the dictionaries, which are basically a map of key-value, the key being a string in general). String operations are slow as hell. One technique of working with Maps (or dictionaries as some languages call them) is hashing strings. The problem is that this is done on demand. Always. So when you access global variable MyVar it first hashes the string (yes, hashing in Lua is extremely fast but it does have overhead), then accessing _G, then searching _G with the hashed value. It sounds dramatic, but you can test and see that it is still fast. It's supposed to be fast. Lua is centered around tables so if tables were slow, what would be the point? But there are other issues. Everything Lua does is saved somewhere. For example loading something with require() is saved in a special library called packages. This holds a table with the same name. Each module is saved in packages (so each time a new module is loaded via require it's put in packages.loaded). But all such libraries are saved somewhere. When using MyVar, it's the same as using _G.MyVar. So anything use by Lua might invariably be put in _G. That table will get bloated in time. The bigger it is the more time it will take to traverse it. Basically no matter how fast table access is, it can never match the speed of stack frame executions.
    Quote Originally Posted by Siesta Guru View Post
    Aren't all variables and functions just things inside of tables?
    Like most languages, Lua has 2 types of data: objects and PODs. PODs are the plain old data types: strings, numbers and possibly bits or bools, depending on the languages. In Lua we have string, number and boolean.
    Objects are more complex types of data. In Lua, objects are functions, threads, tables, userdata. Functions are of different types: global procedures (the "regular" functions saved in _G, accessible from any module), local procedures (the "regular" functions, private to the current module and available only from the current scope onwards) methods (for the OOP approach, passing self, available only if the owning object is accessible), field procedures (a combination between local and global procedures, they are members of tables and exist only within its scope; they are available if the owning table is accessible) closures (inner anonymous functions, available inside a limited scope) . Variables are of many types as well: global variables, local variables and upvalues. Table members (variables or functions) are called "fields". Threads are used with the coroutine library. Userdata are a bunch of raw data holding external information. They usually correspond to C exports. For example units in D2 are not accessible from Lua, therefore, there is a wrapper for them exported into a userdata. Same for spells. Units and spells are not tables.

    Quote Originally Posted by Siesta Guru View Post
    And yeah I've got some infinite loop requires. It doesn't seem to be a problem if there aren't any parse errors though.
    Don't confuse loop requires with circular dependencies. There is a difference between the way files are loaded. require() does something, dofile() does something else. If you are loading lua files with require, what you are actually doing is loading a module (!!!) not a file. Each module is guaranteed to only be loaded once. So if you have the same module required in 6 files, it will only be loaded once and the other 5 calls will have the pointer of the first load returned for efficiency. This is why you are not getting errors. Circular dependency is when a.lua requires b.lua and b.lua requires a.lua. This will never ever work and Lua will return an error. dofile() on the other hand, actually loads the file. There is a difference between a file and a module. Modules also include libraries (meaning dll or so). But obviously, not in D2 But this is a limitation due to security reasons, not in Lua.
    [/quote]

    Quote Originally Posted by Siesta Guru View Post
    - There is some kind of issue happening with the location passability grid. Many spots are shown as passable when they're not, and others are shown as impassable when they're not. This seems to affect both GeneratePath() and IsLocationPassable()
    For example:
    https://imgur.com/a/lZpeV I'm standing on a spot marked impassable, while the spot above me is marked passable yet I can't move there
    https://imgur.com/a/mdCFR There is no spot marked impassable for this tree, while it's obstructing movement
    I reported something similar some time ago. My case involved trees. To me it seemed that it didn't take the collision box into account and just told me what the "map" says is terrain and what isn't. It's true that collision checks are usually expensive (which is why characters usually get boxes or cylinders as physics colliders, and if you shoot a character above the shoulders it registers as a hit even though you know there is only air there) since a mesh collider (something that calculates the actual model) would be expensive as hell. But I am just guessing based on experiments; forcing calls with a few expensive checks determining a location via tree radius and location reduced target location mishaps quite drastically, which is why I assumed it's collision box based. But if that's the case with pathing for bots, then it just makes it harder for us to tell the bots where to go. I asked Chriss for a IsLocationReachable() as well when he has time. There may be a passable location, but there might be 100000 trees blocking the way to it. Also, these functions always assume trees are present as far as I know (need confirmation). So destroying them may not make the bot "smarter" when it comes to pathing - but as I said, need confirmation.

    Quote Originally Posted by Siesta Guru View Post
    It's the cause of a lot of pathing issues for me. Sometimes they think they can't move somewhere and won't, sometimes they think they can move somewhere but the game doesn't allow it
    I will post the function here, but I don't recommend using it in time-critical moments. Did my best to think it in the most optimized way but I am still not satisfied.
    Code:
    local function IsLocationUsable (Unit, Location)
    	if (IsLocationPassable(Location) == false) then
    		return false;
    	end
    
    	local radius = GetUnitToLocationDistance(Unit, Location);
    
    	if (radius > 1500) then
    		return true;
    	end
    
    	local trees = Unit:GetNearbyTrees(radius + 100);
    
    	if ((trees ~= nil) and (#trees > 0)) then
    		for _, tree in pairs(trees) do
    			if (GetTreeLocation(tree) == Location) then
    				return false;
    			elseif (GetLocationToLocationDistanceSqr(Location, GetTreeLocation(tree)) < 400) then		-- 20 units
    				return false;
    			end
    		end
    	end
    
    	return true;
    end
    I wrote it a few months back when I tried to experiment with pathing and ganks. Looking at it now, I think one optimization that should be done is replacing the for ... pairs do with for int ... do. An array indexer will always be faster than an iterator. I am not sure if 20 units is enough to "describe" a tree area, but should be ok to a certain degree. Some fine tuning is easy. Just change 400 to whatever squared value you consider. But as I said, be careful with this function.
    Explanations on the normal, high and very high brackets in replays: here, here & here
    Why maphacks won't work in D2: here

  4. #14
    Basic Member
    Join Date
    Sep 2017
    Posts
    56
    Quote Originally Posted by The Nomad View Post
    What some don't know is about the non-standard way to call a function. You can also do this
    Code:
    StuffModule["MyFunc"]();
    The first thing you're prolly thinking of is "heeeey, wait a minute... I can access indexed lists like this MyTable[4], but I know I can access table members like this MyTable.SomeVar or MyTable["SomeVar"] ... are you saying I can call functions the same way?"
    Yes! Lua is built around tables. Everything is about tables. Except for local variables (and other stuff, but let's simplify it).
    Locally defining stuff in a block, for example in a function, it all gets saved in the stack. Simply put, if the VM is smart enough, it can place part of the data from the stack frame in registers. But even so, anything on the stack is faster, it's all indexed via pointers. For example, when you call a function with variables as params, or call it with hardcoded values as params (what I mean is: v_thing = "Jump"; DoThis(v_thing) or DoThis("Jump")), no variable is passed. Nothing is. It will pass the actual values. So when calling DoThis, only "Jump" is sent.
    Oh that's interesting, huh, I'm starting to like this lua thing. That's pretty clever

    Globals now, are another story. This is because they are stored inside a table. While tables are fast, they aren't as fast as pointers. Pointers are fast because as their name implies, they point to something. They are an address and you know that when you access that pointer (that address) then it returns what is there. There is no "processing" or "searching". It's like an index, like an open directory. Page 6, paragraph 7. Instant hit, similar to searching something at a specific coordinate.
    Tables on the other hand, are of 2 types: indexed (the arrays, which have sequential numbers as keys and store the data in values) and hashed (the dictionaries, which are basically a map of key-value, the key being a string in general). String operations are slow as hell. One technique of working with Maps (or dictionaries as some languages call them) is hashing strings. The problem is that this is done on demand. Always. So when you access global variable MyVar it first hashes the string (yes, hashing in Lua is extremely fast but it does have overhead), then accessing _G, then searching _G with the hashed value. It sounds dramatic, but you can test and see that it is still fast. It's supposed to be fast. Lua is centered around tables so if tables were slow, what would be the point? But there are other issues. Everything Lua does is saved somewhere. For example loading something with require() is saved in a special library called packages. This holds a table with the same name. Each module is saved in packages (so each time a new module is loaded via require it's put in packages.loaded). But all such libraries are saved somewhere. When using MyVar, it's the same as using _G.MyVar. So anything use by Lua might invariably be put in _G. That table will get bloated in time. The bigger it is the more time it will take to traverse it. Basically no matter how fast table access is, it can never match the speed of stack frame executions.
    Alright that makes sense. So, if you get you right: Modules are fine. Locals are fine. Assigning things directly is bad. So do:
    local i = 0;
    but not:
    j = 0;
    Because the latter gets put in _G right?



    Don't confuse loop requires with circular dependencies. There is a difference between the way files are loaded. require() does something, dofile() does something else. If you are loading lua files with require, what you are actually doing is loading a module (!!!) not a file. Each module is guaranteed to only be loaded once. So if you have the same module required in 6 files, it will only be loaded once and the other 5 calls will have the pointer of the first load returned for efficiency. This is why you are not getting errors. Circular dependency is when a.lua requires b.lua and b.lua requires a.lua. This will never ever work and Lua will return an error. dofile() on the other hand, actually loads the file. There is a difference between a file and a module. Modules also include libraries (meaning dll or so). But obviously, not in D2 But this is a limitation due to security reasons, not in Lua.
    Hmm, having followed all of the require chains, apparently I don't have circular dependencies. This comes as a big surprise to me since I've basically just been requiring whatever and am up to 18 files, usually with 6+ requires in each. Strangely enough nothing is circular.



    I reported something similar some time ago. My case involved trees. To me it seemed that it didn't take the collision box into account and just told me what the "map" says is terrain and what isn't. It's true that collision checks are usually expensive (which is why characters usually get boxes or cylinders as physics colliders, and if you shoot a character above the shoulders it registers as a hit even though you know there is only air there) since a mesh collider (something that calculates the actual model) would be expensive as hell. But I am just guessing based on experiments; forcing calls with a few expensive checks determining a location via tree radius and location reduced target location mishaps quite drastically, which is why I assumed it's collision box based. But if that's the case with pathing for bots, then it just makes it harder for us to tell the bots where to go. I asked Chriss for a IsLocationReachable() as well when he has time. There may be a passable location, but there might be 100000 trees blocking the way to it. Also, these functions always assume trees are present as far as I know (need confirmation). So destroying them may not make the bot "smarter" when it comes to pathing - but as I said, need confirmation.
    I can confirm that destroying trees doesn't appear to change what it considers pathable. It's baked straight into the map. And indeed based on my little circle drawing tests it's fully done through 2D squares, which appear to not actually line up with the map.


    I will post the function here, but I don't recommend using it in time-critical moments. Did my best to think it in the most optimized way but I am still not satisfied.
    Had my own version of this going which is honestly way worse. It also checks units and buildings etc, since they don't register for IsLocationPassable() either. It's being executed about ~4-6 ish times per bot per frame which shouldn't be too big of an issue.
    Additionally I'm considering to make a version that checks not just the exact spot, but also points surrounding it so I can know whether my unit will actually fit. Not entirely sure if I should bother though since the IsLocationPassable() function is inaccurate enough anyway that it's still not going to do well. I think we ought to just try to work with the lack of precision collision information
    Last edited by Siesta Guru; 10-04-2017 at 02:46 PM.

  5. #15
    Basic Member
    Join Date
    Mar 2012
    Posts
    2,014
    Quote Originally Posted by Siesta Guru View Post
    Oh that's interesting, huh, I'm starting to like this lua thing. That's pretty clever
    It's mostly useful for dynamic code

    Quote Originally Posted by Siesta Guru View Post
    Alright that makes sense. So, if you get you right: Modules are fine. Locals are fine. Assigning things directly is bad. So do:
    local i = 0;
    but not:
    j = 0;
    Because the latter gets put in _G right?
    Correct. Obviously there might be SOME stuff you need which is not game dependent. I use _G.bDebug and _G.bVerbose for example and some improved exception handling since I don't like the one done by VScript. It also helped me learn more about the way Lua calls work.

    Quote Originally Posted by Siesta Guru View Post
    Had my own version of this going which is honestly way worse. It also checks units and buildings etc, since they don't register for IsLocationPassable() either. It's being executed about ~4-6 ish times per bot per frame which shouldn't be too big of an issue.
    Additionally I'm considering to make a version that checks not just the exact spot, but also points surrounding it so I can know whether my unit will actually fit. Not entirely sure if I should bother though since the IsLocationPassable() function is inaccurate enough anyway that it's still not going to do well. I think we ought to just try to work with the lack of precision collision information
    If you need the collision box of the bot and nearby units, you have GetBoundingRadius(). It returns a float. I believe it's 32 for most heroes. As for optimizations, the call order and checks also count. The one I wrote had a reason for the order of each check. It seemed efficient but I just don't trust it. Lua may be fast, but not as fast as native :P
    Explanations on the normal, high and very high brackets in replays: here, here & here
    Why maphacks won't work in D2: here

  6. #16
    Basic Member
    Join Date
    Dec 2016
    Posts
    180
    Thanks Nomand and Siesta Guru for your ideas. It maybe hard for me now to optimize everything from the beginning but still, there is a long way to establish good bot team and I have to remind myself anytime when i working with modules and local variables.

  7. #17
    Basic Member
    Join Date
    Nov 2011
    Posts
    25
    bot networth becomes 0 at the end result screen

  8. #18
    Basic Member
    Join Date
    Dec 2016
    Posts
    180
    Yup, I think this happens because bots are not steam users or Dota client never consider them as player for calculating their networth however u can find out how much the networth is for each player during GAME_STATE_GAME_IN_PROGRESS

  9. #19
    Basic Member
    Join Date
    Dec 2016
    Posts
    731
    Quote Originally Posted by jstq View Post
    bot networth becomes 0 at the end result screen
    Happens when you play timescale modified bot matches

  10. #20
    Basic Member
    Join Date
    Sep 2017
    Posts
    56
    It seems a lot of neutrals (possibly all?) return 0,0,0 if you call GetLocation on them

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •