Forum

> > CS2D > Scripts > Check for players in range efficiently.
Forums overviewCS2D overview Scripts overviewLog in to reply

English Check for players in range efficiently.

10 replies
To the start Previous 1 Next To the start

old Check for players in range efficiently.

Mami Tomoe
User Off Offline

Quote
It appears that casting this function 20-60~ times per second makes CS2D very sad, but it's required.
Is there a better function that does that this does more efficiently?
Check if theres a player in range of the given tilex and tiley (with sometimes a given range but not always).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function InPlayerRange(tilex, tiley, range)
	if range == 0 then range = CONFIG.SPAWNDISTANCE end
	
	for _, id in pairs(player(0, 'tableliving')) do
		local x, y, dist
		x = (PLAYERS[id].Cords.Tile[1] - tilex) ^ 2
		y = (PLAYERS[id].Cords.Tile[2] - tiley) ^ 2
		dist = math.sqrt(x + y)
		if dist <= range then
			return true
		end
	end
	return false
end

Help would be highly appreciated!

old Re: Check for players in range efficiently.

Gaios
Reviewer Off Offline

Quote
Shouldn't be laggly.

Anyway you can try this:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
function InPlayerRange(tilex, tiley, range)
    range = (range == 0) and CONFIG.SPAWNDISTANCE or range
    
    for _, id in pairs(player(0, 'tableliving')) do
        local x, y, dist
        x = (PLAYERS[id].Cords.Tile[1] - tilex)
        y = (PLAYERS[id].Cords.Tile[2] - tiley)
        dist = math.floor(math.sqrt(x ^ 2 + y ^ 2))
        if dist <= range then
            return true
        end
    end
    return false
end

old Re: Check for players in range efficiently.

DC
Admin Off Offline

Quote
1. I'm not sure if it makes a difference in Lua but in some languages it does: Cache the thing you iterate over in a local variable instead of calling the function in the loop-header.

I'm 100% certain that the
player(0, 'tableliving')
-call is by far the most expensive part.
Because:
a) it's a CS2D API call (which means program flow leaves the Lua VM and goes back)
b) because it has a string parameter. Strings are slow.

If you use it that often you could also cache it in a global variable and only update it on certain events or once every second. Of source this can cause issues though.

2. The math.floor call seems pretty useless here. It depends on the value in
range
though and on the precision you need but I'm pretty sure you don't need a sub pixel precision for this distance check.

3. Micro optimization: Going through tables and accessing arrays costs CPU too. Local variables are the fastest way to access data in nearly every programming language. Including Lua. So instead of getting the tile array twice, just get it once and put it in a local variable.

btw: even doing a
1
2
local sqrt = math.sqrt
local floor = math.floor
and using the local functions instead of the ones in the math table would give you a performance benefit. In theory. In your case you don't have enough calls and probably can't even measure a difference.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function InPlayerRange(tilex, tiley, range)
     if range == 0 then range = CONFIG.SPAWNDISTANCE end
     
     local livingPlayers = player(0, 'tableliving')

     for _, id in pairs(livingPlayers) do
          local x, y, dist
          local playerTile = PLAYERS[id].Cords.Tile
          x = (playerTile[1] - tilex) ^ 2
          y = (playerTile[2] - tiley) ^ 2
          dist = math.sqrt(x + y)
          if dist <= range then
               return true
          end
     end
     return false
end

old Re: Check for players in range efficiently.

Gaios
Reviewer Off Offline

Quote
user DC has written
btw: even doing a
1
2
local sqrt = math.sqrt
local floor = math.floor

But wtf user DC? Making local variables of global variables doesn't make sense, it has the same reference. Also CPU calls RAM and not CPU's Cache for such functions. Or idk.. learn me.

old Re: Check for players in range efficiently.

DC
Admin Off Offline

Quote
@user Gaios: Yes, both, sqrt and math.sqrt hold the same reference.
In this case however it's not important WHAT we get but HOW we get it. Accessing a local variable is simply much faster than accessing a field in a global table.
You can think of sqrt as a shortcut to math.sqrt (or better: a shortcut to the contents of it).

It's a micro optimization though and it won't have a big impact unless the stuff is accessed A LOT (multiple thousands of times).

old Re: Check for players in range efficiently.

Starkkz
Moderator Off Offline

Quote
@user Gaios: Lua attempts to access the local environment before the global environment, the more values a environment contains, the slower it is to access those values. That's how making a global function, a local one, is like making a shortcut to that function.

old Re: Check for players in range efficiently.

Mami Tomoe
User Off Offline

Quote
Is it a good idea to switch all calls for
player(0,'...')
to a table such as SCRIPT.Players... = {...}?
Even if there's multiple files? I can reload the table every time a player dies/spawns (for alive table) to make it so only then will it be accessed.

old Re: Check for players in range efficiently.

DC
Admin Off Offline

Quote
@user Mami Tomoe: Like said before doing this can easily lead to new errors and makes your code more fragile. Also I'm not sure if it will help a lot (you can use Lua time stuff to measure how slow that call is).

I would rather try to reduce the calls to that InPlayerRange function if it's somehow possible. 20-60 times per seconds seems like a lot. Why do you have to do it so frequently?

Edit: I also just decided to add cs2d lua cmd closeplayers and cs2d lua cmd hascloseplayers Lua functions to make such checks faster (available in next version). I think this functionality is required in a lot of cases.
edited 3×, last 25.02.19 10:17:36 pm

old Re: Check for players in range efficiently.

DC
Admin Off Offline

Quote
I'm not sure what that PLAYERS[id].Cords.Tile[1] stuff is. It assume it's a wrapper around even more cs2d lua cmd player calls right?
You could try this:

• only get all (tile) coordinates (x and y) of all living players once every second (or maybe once every 100 ms if you need that level of precision).
• store them in tables
• just use the values from these tables for all your distance checks. This way you can do as many checks as you like without calling any CS2D Lua API commands.

Also distance checks can be optimized by checking the absolute values of (x1-x2) and (y1-y2) first. If one or both of these values are bigger than your distance you can entirely skip the sqrt stuff because it's impossible that the coordinate is within the radius (sqrt is expensive).

old Re: Check for players in range efficiently.

VADemon
User Off Offline

Quote
user DC has written
Also distance checks can be optimized by checking the absolute values of (x1-x2) and (y1-y2) first. If one or both of these values are bigger than your distance you can entirely skip the sqrt stuff because it's impossible that the coordinate is within the radius (sqrt is expensive).

This. In addition, instead of checking for true range
range<=math.sqrt(distance^2)
you can check for range squared:
1
range^2 <= distance^2
Same equation except you need to precompute range^2.

Further: yes, local variables are especially faster in Lua. But it only really makes sense when you call the same variable many times, that's not the case inside your range function.

1
for _, id in pairs(player(0, 'tableliving')) do
the player() function is only called once, because Lua evaluates EVERYTHING to a value first before computation. It has good and bad sides to it.

The "normal" computer science way to optimize this is to divide your whole space into parts and only check adjacent regions for range checks. (like Minecraft chunks)

But honestly, I think you need to rewrite the way you are doing it, I mean the function that calls this function.
Alternatively, only check for nearby monsters onPlayerMovement and activate them for a duration of time. If their duration reaches == 0, check once if there's still a player nearby and make monster inactive (don't tick it any longer) if there're no more players. This way no monsters will be active when the players are too far away or there're no players on the server.
To the start Previous 1 Next To the start
Log in to reply Scripts overviewCS2D overviewForums overview