Getting a team's player count without iterating?

Please post any questions about developing your plugin here. Please use the search function before posting!
User avatar
Doldol
Senior Member
Posts: 200
Joined: Sat Jul 07, 2012 7:09 pm
Location: Belgium

Getting a team's player count without iterating?

Postby Doldol » Fri Nov 13, 2015 10:29 pm

The best way I know of of getting a team's player count is len(PlayerIter(team)), Is there a more efficient way?

I know at least the scoreboard knows this at all times.
User avatar
Ayuto
Project Leader
Posts: 2195
Joined: Sat Jul 07, 2012 8:17 am
Location: Germany

Postby Ayuto » Fri Nov 13, 2015 11:01 pm

We do not provide something else than that. You "could" keep track the player count on your own.

Do you have performance problems?
User avatar
Doldol
Senior Member
Posts: 200
Joined: Sat Jul 07, 2012 7:09 pm
Location: Belgium

Postby Doldol » Fri Nov 13, 2015 11:09 pm

I'd be using it in an active (as in will actively swap people to keep the teams balanced) teambalancer, and I'm ending up with quite a few calls to this, my test server is running fine, but I know I need to be watchful of performance issues when I'd deploy it on the main server.

Isn't there some place the game stores a raw number of how many players are on a team? I tried looking at cs_player_manager but no dice. I'd have no trouble using offsets or even signatures, but I don't know where to look.

But thanks for letting me know!
User avatar
Mahi
Senior Member
Posts: 236
Joined: Wed Aug 29, 2012 8:39 pm
Location: Finland

Postby Mahi » Fri Nov 13, 2015 11:11 pm

Since there's no performance issues yet, I would just go with the len(PlayerIter(team)) solution. Today's computers are way faster than you can even understand, the duration is barely milliseconds. If you end up with performance issues, that's when you figure out the bottleneck and start improving the performance.

Edit: Of course you don't want to intentionally cause performance drain, but stuff like this will quite likely be unnoticeable and you're usually better off just using the obvious solution. Don't go the hard route if there's no reason to :P
User avatar
Doldol
Senior Member
Posts: 200
Joined: Sat Jul 07, 2012 7:09 pm
Location: Belgium

Postby Doldol » Fri Nov 13, 2015 11:23 pm

Mahi wrote:Since there's no performance issues yet, I would just go with the len(PlayerIter(team)) solution. Today's computers are way faster than you can even understand, the duration is probably in milliseconds. If you end up with performance issues, that's when you figure out the bottleneck and start improving the performance.

Edit: Of course you don't want to intentionally cause performance drain, but stuff like this will quite likely be unnoticeable and you're usually better off just using the obvious solution. Don't go the hard route if there's no reason to :P


True, but when you rent a VPS you want to be able to put as many game servers on as few cores as possible without performance issues. Every time you throw away computational power unnecessarily makes you cringe a little.

Additionally there have been times this server with iteration in Eventscripts ran into actual performance issues, on a 3.0Ghz server-grade processor with a full core dedicated to this server. I'd like to avoid that in the future and so far SP has done a really great job at that, and it has allowed the server to have features it couldn't have had before.
User avatar
satoon101
Project Leader
Posts: 2697
Joined: Sat Jul 07, 2012 1:59 am

Postby satoon101 » Fri Nov 13, 2015 11:32 pm

I'm not sure exactly how the scoreboard keeps track. I almost doubt that that is server-side anyway. If you find that it somehow is server-side, you could save the entity(ies) that stores this amount and get the value from it.

You could always use players.PlayerGenerator directly to skip some of the things we do in PlayerIter, but I don't know how much performance difference you could really see out of that.
Image
User avatar
Ayuto
Project Leader
Posts: 2195
Joined: Sat Jul 07, 2012 8:17 am
Location: Germany

Postby Ayuto » Fri Nov 13, 2015 11:42 pm

Just tested the performance between PlayerIter and a specific function to get the player count. Here is the result:

Syntax: Select all

from timeit import Timer

# Tested with 24 bots: 12 T, 12 CT
iterations = 1000

# Result: 2.422217352161965
print(Timer(
"""
len(PlayerIter('t'))
""",
"""
from filters.players import PlayerIter
""").timeit(iterations))


# Result: 0.05735976319851943
print(Timer(
"""
get_player_count(2)
""",
"""
from players import PlayerGenerator
from players.helpers import playerinfo_from_edict

def get_player_count(team):
count = 0
for edict in PlayerGenerator():
info = playerinfo_from_edict(edict)
if info.get_team_index() == team:
count += 1

return count

""").timeit(iterations))
But are you checking the player count on both teams constantly? That would be a performance waste, because there is a "player_team" event that get fired when the teams have changed.
User avatar
Doldol
Senior Member
Posts: 200
Joined: Sat Jul 07, 2012 7:09 pm
Location: Belgium

Postby Doldol » Sat Nov 14, 2015 3:06 am

No not constantly obviously, but I'd like to every time a player dies/disconnects.

But I'm thinking about using CTeam::GetNumPlayers
User avatar
Doldol
Senior Member
Posts: 200
Joined: Sat Jul 07, 2012 7:09 pm
Location: Belgium

Postby Doldol » Sat Nov 14, 2015 4:05 am

This is what I ended up with:
(Excuse all the unneeded/messy imports)

Call get_team_player_count(team_num)
unassigned seemed to return 0 no matter what, so I didn't include it.

Syntax: Select all

from memory import make_object
from memory import DataType
from memory.manager import manager
from memory.manager import CustomType
from memory.hooks import PreHook
from colors import Color
from entities.entity import Entity
from entities.helpers import index_from_pointer, pointer_from_index
from entities.helpers import spawn_entity, create_entity
from core import PLATFORM


from commands import CommandReturn
from commands.client import ClientCommand
from events import Event
from cvars import ConVar
from cvars import cvar
from weapons.entity import WeaponEntity
from entities.helpers import index_from_pointer, pointer_from_inthandle, index_from_inthandle, index_from_edict, pointer_from_index
from entities.helpers import spawn_entity, create_entity
from entities.entity import BaseEntity, Entity
from players.entity import PlayerEntity
from filters.entities import EntityIter
from filters.weapons import WeaponClassIter
from players.constants import LifeState, PlayerButtons
from players.helpers import edict_from_playerinfo, index_from_playerinfo, index_from_userid, userid_from_index, userid_from_playerinfo,playerinfo_from_userid


from listeners import LevelShutdown, LevelInit
from events.manager import event_manager


team_ctype_map = dict()


class CTeam(CustomType, metaclass=manager):
#CTeam::GetNumPlayers(void)
GetNumPlayers = manager.virtual_function(
200 if PLATFORM == 'windows' else 199,
(),
DataType.INT
)


@property
def entity(self):
return Entity(index_from_pointer(self))


def get_team_player_count(team_num):
try: return team_ctype_map[team_num].GetNumPlayers()
except KeyError: return 0


@LevelShutdown
def level_shutdown():
team_ctype_map.clear()


@LevelInit
def level_init(mapname):
print("Level Init")
def level_init_round_start(ev):
print("level_init_round_start")
for cs_team_manager in EntityIter('cs_team_manager', return_types='entity'):
teamnum = cs_team_manager.get_property_int("m_iTeamNum")
if teamnum:
CTeamProto = make_object(CTeam, cs_team_manager.pointer)
team_ctype_map[teamnum] = CTeamProto
event_manager.unregister_for_event("round_start", level_init_round_start)
event_manager.register_for_event("round_start", level_init_round_start)


@Event("round_start")
def demo_trigger(event):
print("round_start")
print(team_ctype_map)
print("Spec", get_team_player_count(1))
print("T", get_team_player_count(2))
print("CT", get_team_player_count(3))


for cs_team_manager in EntityIter('cs_team_manager', return_types='entity'):
teamnum = cs_team_manager.get_property_int("m_iTeamNum")
if teamnum:
CTeamProto = make_object(CTeam, cs_team_manager.pointer)
team_ctype_map[teamnum] = CTeamProto


Speed test: 63 bots, 1 Human (Why does solution #1 complete so fast for me? Different CPU? SP v52 Win 8.1)

Syntax: Select all

from timeit import Timer

# Tested with 24 bots: 12 T, 12 CT
iterations = 1000


# Result: 0.18061759932687965
print(Timer(
"""
len(PlayerIter('t'))
""",
"""
from filters.players import PlayerIter
""").timeit(iterations))




# Result: 0.055757821924498574
print(Timer(
"""
get_player_count(2)
""",
"""
from players import PlayerGenerator
from players.helpers import playerinfo_from_edict


def get_player_count(team):
count = 0
for edict in PlayerGenerator():
info = playerinfo_from_edict(edict)
if info.get_team_index() == team:
count += 1

return count

""").timeit(iterations))


# Result: 0.011191611310351457
print(Timer(
"""
get_team_player_count(2)
""",
"""
# SP Teambalancer
from memory import make_object
from memory import DataType
from memory.manager import manager
from memory.manager import CustomType
from memory.hooks import PreHook
from colors import Color
from entities.entity import Entity
from entities.helpers import index_from_pointer, pointer_from_index
from entities.helpers import spawn_entity, create_entity
from core import PLATFORM


from commands import CommandReturn
from commands.client import ClientCommand
from events import Event
from cvars import ConVar
from cvars import cvar
from weapons.entity import WeaponEntity
from entities.helpers import index_from_pointer, pointer_from_inthandle, index_from_inthandle, index_from_edict, pointer_from_index
from entities.helpers import spawn_entity, create_entity
from entities.entity import BaseEntity, Entity
from players.entity import PlayerEntity
from filters.entities import EntityIter
from filters.weapons import WeaponClassIter
from players.constants import LifeState, PlayerButtons
from players.helpers import edict_from_playerinfo, index_from_playerinfo, index_from_userid, userid_from_index, userid_from_playerinfo,playerinfo_from_userid


from listeners import LevelShutdown, LevelInit
from events.manager import event_manager


team_ctype_map = dict()


class CTeam(CustomType, metaclass=manager):
#CTeam::GetNumPlayers(void)
GetNumPlayers = manager.virtual_function(
200 if PLATFORM == 'windows' else 199,
(),
DataType.INT
)


@property
def entity(self):
return Entity(index_from_pointer(self))


def get_team_player_count(team_num):
try: return team_ctype_map[team_num].GetNumPlayers()
except KeyError: return 0


for cs_team_manager in EntityIter('cs_team_manager', return_types='entity'):
teamnum = cs_team_manager.get_property_int("m_iTeamNum")
if teamnum:
CTeamProto = make_object(CTeam, cs_team_manager.pointer)
team_ctype_map[teamnum] = CTeamProto

""").timeit(iterations))
User avatar
satoon101
Project Leader
Posts: 2697
Joined: Sat Jul 07, 2012 1:59 am

Postby satoon101 » Sat Nov 14, 2015 4:36 am

If you want, you can look into adding that virtual function into our data:
http://forums.sourcepython.com/showthread.php?972

I haven't added the virtual/dynamic function section, but virtuals are listed inside the results files shown at the end of the OP.

I really don't understand why you bother registering/unregistering round_start like you do. Just set the entity variable to None on LevelInit and load and check against that before making it a dictionary on round_start.
Image
User avatar
Doldol
Senior Member
Posts: 200
Joined: Sat Jul 07, 2012 7:09 pm
Location: Belgium

Postby Doldol » Sat Nov 14, 2015 4:51 am

satoon101 wrote:If you want, you can look into adding that virtual function into our data:
http://forums.sourcepython.com/showthread.php?972

I haven't added the virtual/dynamic function section, but virtuals are listed inside the results files shown at the end of the OP.

Sure, I'm fine with that, but I can't test Linux offsets, should I include them anyway?
I also occasionally end up writing up prototypes for functions I end up not using (and so go untested), include those?

satoon101 wrote:I really don't understand why you bother registering/unregistering round_start like you do. Just set the entity variable to None on LevelInit and load and check against that before making it a dictionary on round_start.


If you're referring to the fact that I clear the dictionary on level shutdown, I crash if I keep a reference to CTeam during mapchanges.
Or if you're just referring to the nested function and registering, it seemed cleaner and slightly more intuitive to me, I wouldn't say I'm doing anything really wrong afaik, maybe unusual? + Wouldn't you then also be checking this every round start?
User avatar
Ayuto
Project Leader
Posts: 2195
Joined: Sat Jul 07, 2012 8:17 am
Location: Germany

Postby Ayuto » Sat Nov 14, 2015 11:25 am

I guess the first solutions completes that fast for you, because I'm running the latest SP version and we have recently changed the iterators. While your iterator iterates over Edict objects and uses a different way for filtering, the new iterator iterates over Player objects and accesses dynamic attributes (to check the team), which is currently very performance costly. Making accessing dynamic attributes faster is on my TODO list.

You can't keep the reference to the CTeam objects, because the entities change after a map change. So, your pointer is pointing to an invalid memory address or a different object. You might have luck and it points to the same address, but that's unlikely to happen. Moreover, your code is error prone: what happens if the entity has been re-created during a map. I'm not sure if that happens (it could happen if you use a plugin that re-creates it), but then you will also run into crashes.

Compared to the performance of the second solution I would go with the second solution. If you only check the team count when a player dies or disconnects the performance is negligible. You could even use the first solution. Even if you run it 1000 times in a row on a full server, it's still running fast.
User avatar
Doldol
Senior Member
Posts: 200
Joined: Sat Jul 07, 2012 7:09 pm
Location: Belgium

Postby Doldol » Sat Nov 14, 2015 7:51 pm

Ayuto wrote:I guess the first solutions completes that fast for you, because I'm running the latest SP version and we have recently changed the iterators. While your iterator iterates over Edict objects and uses a different way for filtering, the new iterator iterates over Player objects and accesses dynamic attributes (to check the team), which is currently very performance costly. Making accessing dynamic attributes faster is on my TODO list.

You can't keep the reference to the CTeam objects, because the entities change after a map change. So, your pointer is pointing to an invalid memory address or a different object. You might have luck and it points to the same address, but that's unlikely to happen. Moreover, your code is error prone: what happens if the entity has been re-created during a map. I'm not sure if that happens (it could happen if you use a plugin that re-creates it), but then you will also run into crashes.

Compared to the performance of the second solution I would go with the second solution. If you only check the team count when a player dies or disconnects the performance is negligible. You could even use the first solution. Even if you run it 1000 times in a row on a full server, it's still running fast.


You're right, I agree :) & I'm happy a speed improvement for #1 is planned for the times we do need that.

Return to “Plugin Development Support”

Who is online

Users browsing this forum: No registered users and 47 guests