Results 1 to 9 of 9

Thread: BUG(???): bot_generic.lua share local variables between bots

  1. #1
    Basic Member
    Join Date
    Dec 2016
    Posts
    180

    BUG(???): bot_generic.lua share local variables between bots

    Let me explain what goes on here:
    consider I have 5 bot player who they follow what I command in bot_generic file. everything goes fine because using CreateGeneric(), every player has its own generic table and local variables are not sharable.

    when I implement generic file for specific hero like bot_sniper, all algorithms run in a right way bot when in add other hero's file like bot_dazzle, everything crumbled.

    after many days i recognized that i Used a variable called subitems like this:
    Code:
    local index =1 
    local subitems = {}
    .
    .
    .
    function BuyThis( item , ...  )
    ... some changes affect subitems and index
    end
    only one thing here comes to my mind and I check item purchasing of entities, CreateGeneric just append functions to output variable defined in bot_generic but not local variables. i already append all local variables to returned variable in bot_generic.lua but dota crash.

    Bug or not, any suggestion for encountering this issue?

  2. #2
    Basic Member
    Join Date
    Mar 2012
    Posts
    2,013
    I doubt this is caused by allocating variables and functions to bots. This seems like a design issue. If a table grows in size exponentially, this is not related to the API but to design.
    Explanations on the normal, high and very high brackets in replays: here, here & here
    Why maphacks won't work in D2: here

  3. #3
    Basic Member
    Join Date
    Dec 2016
    Posts
    180
    honestly, this is related to design rather than API but is there anyway to resolve this issue? I mean , to create local variables instantly seperate for each entity?
    tables are not shared but local values like numbers ae shared. it seems that they passed as values not referenced.
    Last edited by SIKIM; 08-04-2017 at 06:13 AM.

  4. #4
    Basic Member
    Join Date
    Mar 2012
    Posts
    2,013
    Only tables and userdata is passed by reference. PODs (primitives like int, float, bool etc) will always be passed by value so the behavior is correct. Local variables are akin to static variables that are "called" to be set only once: when that file is loaded (i.e. using require).


    If you want to use variables that are different for each bot you will have to either assign them to the bot (i.e. GetBot().subitems = {} ) as some botmakers do or you can create a wrapper then link them in a similar way. For the wrapper alternative, while using OOP is how I used to do it (and how nostrademous does it), there is also a simpler approach which does the same thing: assign a table to the bot where you save stuff (f.e. GetBot().LocalVars = {}, GetBot().LocalVars.subitems = {} ). On the long run they are both the same thing. It's just a design thing.

    Here is one of my earlier methods using OOP:
    Code:
    -- bot_generic.lua
    GenericBot = {}
    
    -- Methods
    
    -- constructs the object
    function GenericBot:Constructor ()
    	-- internals
    	self.bInitialized = false; 							-- false until initialized; the initialization happens after picking a hero
    	self.internal = GetBot(); 							-- pointer to the internal native bot
    	self.internal.instance = self;	 					-- pointer to the LUA bot instance
    	self.UnitName = self.internal:GetUnitName(); 		-- the name of the unit
    	self.Coach = nil;
    	self.Brain = nil;
    
    	-- member variables
    	self.HomeLocation = nil;	 						-- the home base
    	self.LastTower = nil; 								-- the last tower that the hero was in range of
    	self.LastEnemyHero = nil; 							-- last enemy hero targeted by this bot
    	self.LastTarget = nil;	 							-- the last target of this bot (may be a hero, creep, tree, ward etc.)
    	self.TargetUnit = nil;								-- the current target of this bot (may be a hero, creep, tree, ward etc.)
    	self.TargetLocation = nil;							-- the target location
    	self.TargetRune = nil;								-- the desired rune
    	self.DesiredFarmingLocation = nil;					-- desired farming spot
    	self.LastFarmingLocation = nil;						-- previous farming spot
    	self.RetreatDestination = nil;						-- retreat destination
    	self.LastActiveMode = nil;							-- the previous mode
    	self.ActiveMode = nil;								-- the current mode
    	self.AssignedLane = nil;							-- the assigned lane
    
    	return self;
    end
    
    -- Variables
    local Bot = new(GenericBot);
    
    -- Functions
    
    -- called every frame, responsible for bot logic
    function Think ()
    	if (Bot ~= nil) then
    		Bot:Update(); -- this is where the logic was done
    	end
    end
    
    -- some functions I had:
    -- returns the ability list
    -- return: the list of abilities
    function GenericBot:GetAbilities () end
    
    -- returns the ability that allows this unit to teleport
    -- return: the ability
    function GenericBot:GetTeleportationAbility ()
    	if (Unit.HasNativeTeleport(self.UnitName) == false) then
    		return nil;
    	end
    
    	for _, ability in pairs(GetAbilities()) do
    		if (ability.bIsTeleport == true) then
    			return ability;
    		end	
    	end
    
    	return nil;
    end
    
    -- returns the ability that allows this unit to blink (includes pseudo-blinks)
    -- return: the ability
    function GenericBot:GetBlinkAbility ()
    	if (Unit.HasNativeBlink(self.UnitName) == false) then
    		return nil;
    	end
    
    	for _, ability in pairs(GetAbilities()) do
    		if (ability.bIsBlink == true) then
    			return ability;
    		end	
    	end
    
    	return nil;
    end
    
    function GenericBot:Init ()
    	self.LastUpdate = RealTime();
    	self.HomeLocation = self.internal:GetLocation();
    end
    
    -- bot_legion_commander
    LegionCommander = {}
    
    -- Variables
    local Bot = new(LegionCommander, GenericBot);
    local Abilities = {}
    
    -- Methods
    
    -- initializes the object
    function LegionCommander:Init ()
    	local abilityName;
    
    	self.super.Init(self);
    
    	abilityName = "legion_commander_overwhelming_odds";
    	Abilities[abilityName] = {}
    	Abilities[abilityName].Spell = new (LCOverwhelmingOdds, AIAbility);
    	Abilities[abilityName].SlotID = 1;	
    	Abilities[abilityName].Spell:Init(self.internal:GetAbilityByName(abilityName));
    
    	abilityName = "legion_commander_press_the_attack";
    	Abilities[abilityName] = {}
    	Abilities[abilityName].Spell = new (LCPressTheAttack, AIAbility);
    	Abilities[abilityName].SlotID = 2;	
    	Abilities[abilityName].Spell:Init(self.internal:GetAbilityByName(abilityName));
    
    	abilityName = "legion_commander_duel";
    	Abilities[abilityName] = {}
    	Abilities[abilityName].Spell = new (LCDuel, AIAbility);
    	Abilities[abilityName].SlotID = 4;	
    	Abilities[abilityName].Spell:Init(self.internal:GetAbilityByName(abilityName));
    
    	self.internal:ActionImmediate_Chat("I am " .. self.UnitName, false);
    	print("Legion Commander is init")
    end
    
    
    -- returns the ability list
    -- return: the list of abilities
    function LegionCommander:GetAbilities () 
    	return Abilities;
    end
    
    -- Functions
    
    -- called every frame, responsible for bot logic
    function Think ()
    	if (Bot ~= nil) then
    		Bot:Update();
    	end
    end

    This is the function:
    Code:
    function new (Child, Parent)
    	local instance;
    
    	if (Parent == nil) then
    		instance = setmetatable({}, {__index = Child});
    	else
    		instance =  {super = 	Parent};
    		setmetatable(Child, 	{__index = Parent});
    		setmetatable(instance, 	{__index = Child});
    	end
    
    	return instance:Constructor();
    end
    Basically GenericBot is a base class with common code or empty placeholder functions and each "actual" bot (like Legion) would then be a specialized version of local instances (each one obviously different). And using the super handle, I could take advantage of OOP design and indirectly of polymorphism for functions that expected GenericBot as parameters

    Then any variable set in GenricBot would be "local" to that instance. If a bot was not implemented, it'd fallback to GenericBot, but each local Bot would still be different because the variable was only used once: at the VM init

    While that approach had a lot of advantages, in order to simplify stuff (using self.instance and self.internal constantly made it hard to keep track of what I needed: the actual bot handle or my bot handle?), I switched to the first alternative; assigning variables on the handle returned by GetBot(). The code was simplified a lot and gave me more room to use code more dynamically. And because the handle is the same for ALL types of units (towers, couriers, minions, heroes, creeps), I don't need polymorphism. In the end, switching between them back and forth is not that hard. As long as you keep track of what functions need: i.e. you can't use ActionImmediate_Chat() on your wrapper, you need the GetBot() handle.

    If you decide to use wrappers, you'll see I cached some calls I would perform a lot (such as GetBot()). Nostrademous uses a table for bots where he stores stuff to use, for example, between modes or files, but also between functions (such as results from iterations like GetNearby*() which is recommended to avoid performance degradation)

    Ah, forgot to mention one disadvantage of using wrappers: they'll end up as tables inside _G.
    That was the drawback at this earlier draft (notice GenericBot() is global). Even if I do set GetBot().instance = self, only the actual pointer is saved inside the bot handle, not the actual table). The good thing was I didn't overload it TOO much
    Explanations on the normal, high and very high brackets in replays: here, here & here
    Why maphacks won't work in D2: here

  5. #5
    Quote Originally Posted by SIKIM View Post
    Let me explain what goes on here:
    consider I have 5 bot player who they follow what I command in bot_generic file. everything goes fine because using CreateGeneric(), every player has its own generic table and local variables are not sharable.

    when I implement generic file for specific hero like bot_sniper, all algorithms run in a right way bot when in add other hero's file like bot_dazzle, everything crumbled.

    after many days i recognized that i Used a variable called subitems like this:
    Code:
    local index =1 
    local subitems = {}
    .
    .
    .
    function BuyThis( item , ...  )
    ... some changes affect subitems and index
    end
    only one thing here comes to my mind and I check item purchasing of entities, CreateGeneric just append functions to output variable defined in bot_generic but not local variables. i already append all local variables to returned variable in bot_generic.lua but dota crash.

    Bug or not, any suggestion for encountering this issue?
    Generic modes/files from the API are called with the require function (or something similar). Manually define them and use dofile instead to have actual local variables/functions for different heroes.

  6. #6
    Basic Member
    Join Date
    Dec 2016
    Posts
    180
    Quote Originally Posted by The Nomad View Post
    Only tables and userdata is passed by reference. PODs (primitives like int, float, bool etc) will always be passed by value so the behavior is correct. Local variables are akin to static variables that are "called" to be set only once: when that file is loaded (i.e. using require).


    If you want to use variables that are different for each bot you will have to either assign them to the bot (i.e. GetBot().subitems = {} ) as some botmakers do or you can create a wrapper then link them in a similar way. For the wrapper alternative, while using OOP is how I used to do it (and how nostrademous does it), there is also a simpler approach which does the same thing: assign a table to the bot where you save stuff (f.e. GetBot().LocalVars = {}, GetBot().LocalVars.subitems = {} ). On the long run they are both the same thing. It's just a design thing.

    Here is one of my earlier methods using OOP:
    Code:
    -- bot_generic.lua
    GenericBot = {}
    
    -- Methods
    
    -- constructs the object
    function GenericBot:Constructor ()
    	-- internals
    	self.bInitialized = false; 							-- false until initialized; the initialization happens after picking a hero
    	self.internal = GetBot(); 							-- pointer to the internal native bot
    	self.internal.instance = self;	 					-- pointer to the LUA bot instance
    	self.UnitName = self.internal:GetUnitName(); 		-- the name of the unit
    	self.Coach = nil;
    	self.Brain = nil;
    
    	-- member variables
    	self.HomeLocation = nil;	 						-- the home base
    	self.LastTower = nil; 								-- the last tower that the hero was in range of
    	self.LastEnemyHero = nil; 							-- last enemy hero targeted by this bot
    	self.LastTarget = nil;	 							-- the last target of this bot (may be a hero, creep, tree, ward etc.)
    	self.TargetUnit = nil;								-- the current target of this bot (may be a hero, creep, tree, ward etc.)
    	self.TargetLocation = nil;							-- the target location
    	self.TargetRune = nil;								-- the desired rune
    	self.DesiredFarmingLocation = nil;					-- desired farming spot
    	self.LastFarmingLocation = nil;						-- previous farming spot
    	self.RetreatDestination = nil;						-- retreat destination
    	self.LastActiveMode = nil;							-- the previous mode
    	self.ActiveMode = nil;								-- the current mode
    	self.AssignedLane = nil;							-- the assigned lane
    
    	return self;
    end
    
    -- Variables
    local Bot = new(GenericBot);
    
    -- Functions
    
    -- called every frame, responsible for bot logic
    function Think ()
    	if (Bot ~= nil) then
    		Bot:Update(); -- this is where the logic was done
    	end
    end
    
    -- some functions I had:
    -- returns the ability list
    -- return: the list of abilities
    function GenericBot:GetAbilities () end
    
    -- returns the ability that allows this unit to teleport
    -- return: the ability
    function GenericBot:GetTeleportationAbility ()
    	if (Unit.HasNativeTeleport(self.UnitName) == false) then
    		return nil;
    	end
    
    	for _, ability in pairs(GetAbilities()) do
    		if (ability.bIsTeleport == true) then
    			return ability;
    		end	
    	end
    
    	return nil;
    end
    
    -- returns the ability that allows this unit to blink (includes pseudo-blinks)
    -- return: the ability
    function GenericBot:GetBlinkAbility ()
    	if (Unit.HasNativeBlink(self.UnitName) == false) then
    		return nil;
    	end
    
    	for _, ability in pairs(GetAbilities()) do
    		if (ability.bIsBlink == true) then
    			return ability;
    		end	
    	end
    
    	return nil;
    end
    
    function GenericBot:Init ()
    	self.LastUpdate = RealTime();
    	self.HomeLocation = self.internal:GetLocation();
    end
    
    -- bot_legion_commander
    LegionCommander = {}
    
    -- Variables
    local Bot = new(LegionCommander, GenericBot);
    local Abilities = {}
    
    -- Methods
    
    -- initializes the object
    function LegionCommander:Init ()
    	local abilityName;
    
    	self.super.Init(self);
    
    	abilityName = "legion_commander_overwhelming_odds";
    	Abilities[abilityName] = {}
    	Abilities[abilityName].Spell = new (LCOverwhelmingOdds, AIAbility);
    	Abilities[abilityName].SlotID = 1;	
    	Abilities[abilityName].Spell:Init(self.internal:GetAbilityByName(abilityName));
    
    	abilityName = "legion_commander_press_the_attack";
    	Abilities[abilityName] = {}
    	Abilities[abilityName].Spell = new (LCPressTheAttack, AIAbility);
    	Abilities[abilityName].SlotID = 2;	
    	Abilities[abilityName].Spell:Init(self.internal:GetAbilityByName(abilityName));
    
    	abilityName = "legion_commander_duel";
    	Abilities[abilityName] = {}
    	Abilities[abilityName].Spell = new (LCDuel, AIAbility);
    	Abilities[abilityName].SlotID = 4;	
    	Abilities[abilityName].Spell:Init(self.internal:GetAbilityByName(abilityName));
    
    	self.internal:ActionImmediate_Chat("I am " .. self.UnitName, false);
    	print("Legion Commander is init")
    end
    
    
    -- returns the ability list
    -- return: the list of abilities
    function LegionCommander:GetAbilities () 
    	return Abilities;
    end
    
    -- Functions
    
    -- called every frame, responsible for bot logic
    function Think ()
    	if (Bot ~= nil) then
    		Bot:Update();
    	end
    end

    This is the function:
    Code:
    function new (Child, Parent)
    	local instance;
    
    	if (Parent == nil) then
    		instance = setmetatable({}, {__index = Child});
    	else
    		instance =  {super = 	Parent};
    		setmetatable(Child, 	{__index = Parent});
    		setmetatable(instance, 	{__index = Child});
    	end
    
    	return instance:Constructor();
    end
    Basically GenericBot is a base class with common code or empty placeholder functions and each "actual" bot (like Legion) would then be a specialized version of local instances (each one obviously different). And using the super handle, I could take advantage of OOP design and indirectly of polymorphism for functions that expected GenericBot as parameters

    Then any variable set in GenricBot would be "local" to that instance. If a bot was not implemented, it'd fallback to GenericBot, but each local Bot would still be different because the variable was only used once: at the VM init

    While that approach had a lot of advantages, in order to simplify stuff (using self.instance and self.internal constantly made it hard to keep track of what I needed: the actual bot handle or my bot handle?), I switched to the first alternative; assigning variables on the handle returned by GetBot(). The code was simplified a lot and gave me more room to use code more dynamically. And because the handle is the same for ALL types of units (towers, couriers, minions, heroes, creeps), I don't need polymorphism. In the end, switching between them back and forth is not that hard. As long as you keep track of what functions need: i.e. you can't use ActionImmediate_Chat() on your wrapper, you need the GetBot() handle.

    If you decide to use wrappers, you'll see I cached some calls I would perform a lot (such as GetBot()). Nostrademous uses a table for bots where he stores stuff to use, for example, between modes or files, but also between functions (such as results from iterations like GetNearby*() which is recommended to avoid performance degradation)

    Ah, forgot to mention one disadvantage of using wrappers: they'll end up as tables inside _G.
    That was the drawback at this earlier draft (notice GenericBot() is global). Even if I do set GetBot().instance = self, only the actual pointer is saved inside the bot handle, not the actual table). The good thing was I didn't overload it TOO much
    Thanks for your help. making super and child Pseudo-class via changing metatable is a clever trick. this will make code design more flexible but with some disadvantages that you mentioned above.
    I already worked with many programming languages but LUA is more complicated than I expected and I try to learn more about it. what I concern about that is making huge data in late game. currently VM drops a warning in console that script is using huge data on RAM( at most 2 GB !!). Actually i did nothing but implementing bot_generic and some utility and team functions( coach, teamwork and etc. ) annexing extra data to GetBot() is a lovely trick but i dont have idea about the future of 5 GetBot() entities with extra variables.
    Anyway this will be useful for me and those who are encountering with this issue. Massive thanks...

  7. #7
    Basic Member
    Join Date
    Dec 2016
    Posts
    180
    Quote Originally Posted by Platinum_dota2 View Post
    Generic modes/files from the API are called with the require function (or something similar). Manually define them and use dofile instead to have actual local variables/functions for different heroes.
    Thanks

    i already didi that like what valve give extra information Here. but the issue is accessing to local variables which are shared to all bots and thats what make most algorithms runs in a wrong way. Nomad gives some good tricks and suggestion and i will implement them tonight.

  8. #8
    Basic Member
    Join Date
    Mar 2012
    Posts
    2,013
    Quote Originally Posted by SIKIM View Post
    Thanks for your help. making super and child Pseudo-class via changing metatable is a clever trick. this will make code design more flexible but with some disadvantages that you mentioned above.
    I already worked with many programming languages but LUA is more complicated than I expected and I try to learn more about it.
    What initially threw me off is that it doesn't have any classes, structures or variables in the actual sense. Just tables with keys and values. Once you learn that, metatables is the concept that will give you headaches After that, there's userdata.
    But in the end it's not very complicated. Luckily it has a lot of common ground which is why it's so versatile (in the end you realise that everything that's on the heap is stored in _G one way or another, except for whats on the stack, of course).

    Quote Originally Posted by SIKIM View Post
    Twhat I concern about that is making huge data in late game. currently VM drops a warning in console that script is using huge data on RAM( at most 2 GB !!). Actually i did nothing but implementing bot_generic and some utility and team functions( coach, teamwork and etc. ) annexing extra data to GetBot() is a lovely trick but i dont have idea about the future of 5 GetBot() entities with extra variables.
    Anyway this will be useful for me and those who are encountering with this issue. Massive thanks...
    As long as you don't go overboard there shouldn't be a warning. Since you keep mentioning the RAM usage and warnings, then the problem in your code might actually be allocation. There is clearly an allocation somewhere the grows exponentially, most likely in an iterator.
    Perhaps if you get it in a working state, maybe try to upload the alpha somewhere so we can check it out if you wish. Until then, I think the alternative is this:
    - turn off all bots except one (just comment their Think() )
    - get a medium duration of when the message appears (say after 11-5 minutes of gameplay) after you test with at least 10-15 games to get proper statistics
    - turn off some features, then let the game work (even if the bot isn't doing anything) until you get to the same amount of time (you can even include turning off movement)
    - then reenable one (for example only the bot that jungles) and wait 11-15 minutes and see what happens
    - then reenable 1 by 1 and check again until you see the cause (i.e. the item list to buy, nearby enemies, abilities, targeting etc.)

    I don't think it's normal to have 2GB in a single table

    Quote Originally Posted by SIKIM View Post
    Thanks

    i already didi that like what valve give extra information Here. but the issue is accessing to local variables which are shared to all bots and thats what make most algorithms runs in a wrong way. Nomad gives some good tricks and suggestion and i will implement them tonight.
    You can't save anything dynamic there that will be shared between instances because it is static (therefore it has no concept of hierarchy or instancing). Valve uses a clever trick using the G table to have several pseudo-instances (for lack of a better work) of the Think() function, one for each bot, even though all 5 are called bot_generic, but that is a global function. If a variable is local, it's local. There is no way to "run" it multiple times and you can't access it from _G either (it's local !). As I said, it's only called once when the module is loaded. So once they switch context, it's no longer possible.
    Last edited by The Nomad; 08-05-2017 at 04:05 AM.
    Explanations on the normal, high and very high brackets in replays: here, here & here
    Why maphacks won't work in D2: here

  9. #9
    Basic Member
    Join Date
    Dec 2016
    Posts
    180
    - Yes of course these are not classes but developer can expand them as a class
    - it is a good idea to check all features one by one. i already spending a month for just revising my last-hit algorithm.
    - as you said, all variables saved as local but seperately from each other( subitems and some others like that ). maybe this make misunderstanding but I meant same as you

Posting Permissions

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