Extending the plugin with C++

Please post any questions about developing your plugin here. Please use the search function before posting!
User avatar
BackRaw
Senior Member
Posts: 537
Joined: Sun Jul 15, 2012 1:46 am
Location: Germany
Contact:

Extending the plugin with C++

Postby BackRaw » Mon Jul 20, 2015 9:29 pm

Hi everyone,

I'm thinking about converting an already existing Python code (spawnpoints module, part of a SP Plugin) to C++ - just to learn more of the API. Here's the code:

Syntax: Select all

# ../seekme/spawnpoints.py

"""Provides a dynamic way to save and load map-based spawn points."""

# =============================================================================
# >> IMPORTS
# =============================================================================
# Python Imports
# JSON
from json import dump as json_dump
from json import load as json_load
# OS
from os import makedirs
# Random
from random import choice as random_choice

# Source.Pyton Imports
# Engines
from engines.server import global_vars
# Filters
from filters.players import PlayerIter
# Mathlib
from mathlib import Vector
# Paths
from paths import PLUGIN_DATA_PATH

# Script Imports
from .info import info


# =============================================================================
# >> CONSTANTS
# =============================================================================
# Miniimum distance for spawning players
DISTANCE_SPAWN = 100.0

# Minimum distance to add vectors
DISTANCE_ADD = 200.0

# Maximum number of vectors per map (5 times 'maxplayers')
VECTORS_MAX = global_vars.max_clients * 5


# =============================================================================
# >> GLOBAL VARIABLES
# =============================================================================
# PlayerIter() generator for alive players' locations
playeriter_alive_origins = PlayerIter(is_filters='alive', return_types='location')

# ../addons/source-python/data/plugins/flashfunsp/spawnpoints
spawnpoints_path = PLUGIN_DATA_PATH / info.basename / 'spawnpoints'


# =============================================================================
# >> CLASSES
# =============================================================================
class _SpawnPoints(list):

""" Extends list for handling Vector instances as spawnpoints. """

def __init__(self):
"""Calls list's constructor."""
super(_SpawnPoints, self).__init__()

def append(self, vector):
"""Checks the size of this list before appending the vector."""
# Does the vector already exist?
if vector in self:

# If yes, don't go any further
return

# Do we have enough vectors for the map?
if len(self) >= VECTORS_MAX:

# If yes, don't go any further
return

# If not, add the vector to the list
super(_SpawnPoints, self).append(vector)

def load(self):
"""Loads the spawnpoints/<mapname>.json file if it exists and adds the vectors to this list."""
# Clear the list if it's not empty
if self:
self.clear()

# Does the file exist?
if not self.json_path.isfile():

# If not, don't go any further
return

# Else, open the file for reading
with self.json_path.open() as file_object:

# store the file's contents (lists of [X,Y,Z] coordinates)
contents = json_load(file_object)

# loop through the file's contents
for coords in contents:

# Append the coordinates as a Vector instance
self.append(Vector(*coords))

def save(self):
"""Writes vectors from this list to the spawnpoints/<mapname>.json file."""
# Do we have anything to save?
if not self:

# If not, don't go any further
return

# Does to folder to save the file in exist?
if not spawnpoints_path.exists():

# If not, create the path tree
makedirs(spawnpoints_path)

# Convert the Vector instances of this list into lists of [X, Y, Z] coordinates
coords = list(map(lambda vector: [vector.x, vector.y, vector.z], self))

# Open the file for writing
with self.json_path.open("w") as file_object:

# Dump the [X,Y,Z] lists to it
json_dump(coords, file_object, indent=4)

def check(self, vector):
"""Returns whether the vector can be added to this list."""
# Is the vector already in this list?
if vector in self:

# return False to stop the addition
return False

# Loop through each vector in this list
for check in self:

# Is the check vector too near?
if check.get_distance(vector) < DISTANCE_ADD:

# If yes, return False
return False

# else return True to add it
return True

def get_random(self, tried=0):
"""Returns a random spawn-point."""
# Did we try enough times?
if tried == len(self):

# If yes, we couldn't find a vector, so return None
return None

# Get a random vector
vector = random_choice(self)

# Is any player too near to this vector?
for origin in playeriter_alive_origins:
if vector.get_distance(origin) < DISTANCE_SPAWN:

# If yes, try another time
return self.get_random(tried + 1)

# If not, return the chosen vector
return vector

@property
def json_path(self):
return spawnpoints_path.joinpath('{0}.json'.format(global_vars.map_name))


# Create an instance of the _SpawnPoints class
spawnpoints = _SpawnPoints()
As you can see, the only thing needed by the script is basically the path to save the spawn points. However, this could be passed to the SpawnPoints class aswell.
So, how would I go about turning this into a SP Plugin extension?

Thanks! :D

EDIT: I'm not asking about my code's logic, rather the structure of how I would use includes and the boost library and stuff.
My Github repositories:

Source.Python: https://github.com/backraw
User avatar
Ayuto
Project Leader
Posts: 2195
Joined: Sat Jul 07, 2012 8:17 am
Location: Germany

Postby Ayuto » Wed Jul 22, 2015 4:44 pm

I wanted to reply earlier, but forgot to do that. :D

Which API do you mean? The Source SDK or our API? If you mean our API, you better use SP directly.
User avatar
BackRaw
Senior Member
Posts: 537
Joined: Sun Jul 15, 2012 1:46 am
Location: Germany
Contact:

Postby BackRaw » Wed Jul 22, 2015 9:05 pm

Ayuto wrote:I wanted to reply earlier, but forgot to do that. :D

Which API do you mean? The Source SDK or our API? If you mean our API, you better use SP directly.


Yeah the SP C++ API. I just want to learn how it works so I could extend my plugin with heavy math stuff or something else later on written in C++.
My Github repositories:

Source.Python: https://github.com/backraw
User avatar
Doldol
Senior Member
Posts: 200
Joined: Sat Jul 07, 2012 7:09 pm
Location: Belgium

Postby Doldol » Wed Jul 22, 2015 9:15 pm

You'd generally just write your code in C, compile it into a library (think .dll/.so), and load it with python using something like the ctypes library.

This isn't SP specific, it's applicable to most Python applications.

If you want to modify SP, you'll have to create your own fork, compile it yourself and keep that up to date. Which is not what you seem to want to do.
User avatar
Ayuto
Project Leader
Posts: 2195
Joined: Sat Jul 07, 2012 8:17 am
Location: Germany

Postby Ayuto » Wed Jul 22, 2015 9:34 pm

Well, on the C++ side we actually just wrap classes/functions of the SDK and create compatibility functions. And everything we have created is exposed to Python. If you do calculations with a high performance cost, I would implement them in Python at first. If you feel that it slows down your server, you can still create a module in C++ that does the calculations and doesn't rely on the Source SDK or our API. So, you would create a Python plugin, which uses this module to do the calculations.

If you want you can take a look at binutils (our memory module is based on that). It's far away from being perfect, but at least it shows how to create a Python module in C++ that runs independently of the Source SDK. You could even use it with a fresh Python interpreter installation.
User avatar
BackRaw
Senior Member
Posts: 537
Joined: Sun Jul 15, 2012 1:46 am
Location: Germany
Contact:

Postby BackRaw » Wed Jul 22, 2015 9:37 pm

Ayuto wrote:Well, on the C++ side we actually just wrap classes/functions of the SDK and create compatibility functions. And everything we have created is exposed to Python. If you do calculations with a high performance cost, I would implement them in Python at first. If you feel that it slows down your server, you can still create a module in C++ that does the calculations and doesn't rely on the Source SDK or our API. So, you would create a Python plugin, which uses this module to do the calculations.

If you want you can take a look at binutils (our memory module is based on that). It's far away from being perfect, but at least it shows how to create a Python module in C++ that runs independently of the Source SDK. You could even use it with a fresh Python interpreter installation.


Alright, good to know, thanks :D
My Github repositories:

Source.Python: https://github.com/backraw
User avatar
satoon101
Project Leader
Posts: 2697
Joined: Sat Jul 07, 2012 1:59 am

Postby satoon101 » Thu Jul 23, 2015 12:50 am

I understand the purpose here is to help you write a C++ extension, but I thought I would discuss the code from a Python standpoint.

First, and very minor, there are couple things around your Path usage that I thought I would point out.

Syntax: Select all

# Does to folder to save the file in exist?
if not spawnpoints_path.exists():

You should be using Path.isdir() in this situation. There could be (although unlikely) a file with the name spawnpoints (and no extension) in the directory, which would cause the code to not create the spawnpoints directory. Also:

Syntax: Select all

# If not, create the path tree
makedirs(spawnpoints_path)

Instead of importing makedirs, just use the Path.makedirs() method.



Now, to the main part of this post. There is definitely flawed logic in your get_random method. Each time it is called, it gets a random value from the list. There is certainly no guarantee that each point in the list would even be checked against, as the same value could be chosen at random multiple times. I think instead it would be better to randomize the list itself and then iterate through all the values:

Syntax: Select all

from random import shuffle


def get_random(self):
"""Return a random spawn-point."""
# Randomize the list by shuffling it
shuffle(self)

# Loop through the now randomized spawnpoints
for vector in self:

# Loop through all alive player origins
for origin in playeriter_alive_origins:

# Is the player too close to this vector?
if vector.get_distance(origin) < DISTANCE_SPAWN:

# Break the player origin loop to move to the next spawnpoint
break

# Was the player origin loop not broken?
else:

# Return the spawnpoint
return vector

# Return None if no spawnpoint was available
return None


That code is untested, but I believe it should work.
Image
User avatar
BackRaw
Senior Member
Posts: 537
Joined: Sun Jul 15, 2012 1:46 am
Location: Germany
Contact:

Postby BackRaw » Thu Jul 23, 2015 1:45 am

satoon101 wrote:I understand the purpose here is to help you write a C++ extension, but I thought I would discuss the code from a Python standpoint.

First, and very minor, there are couple things around your Path usage that I thought I would point out.

Syntax: Select all

# Does to folder to save the file in exist?
if not spawnpoints_path.exists():

You should be using Path.isdir() in this situation. There could be (although unlikely) a file with the name spawnpoints (and no extension) in the directory, which would cause the code to not create the spawnpoints directory. Also:

Syntax: Select all

# If not, create the path tree
makedirs(spawnpoints_path)

Instead of importing makedirs, just use the Path.makedirs() method.

True, I haven't looked at Path really. Thanks for the heads up!


satoon101 wrote:Now, to the main part of this post. There is definitely flawed logic in your get_random method. Each time it is called, it gets a random value from the list. There is certainly no guarantee that each point in the list would even be checked against, as the same value could be chosen at random multiple times. I think instead it would be better to randomize the list itself and then iterate through all the values:

Syntax: Select all

from random import shuffle


def get_random(self):
"""Return a random spawn-point."""
# Randomize the list by shuffling it
shuffle(self)

# Loop through the now randomized spawnpoints
for vector in self:

# Loop through all alive player origins
for origin in playeriter_alive_origins:

# Is the player too close to this vector?
if vector.get_distance(origin) < DISTANCE_SPAWN:

# Break the player origin loop to move to the next spawnpoint
break

# Was the player origin loop not broken?
else:

# Return the spawnpoint
return vector

# Return None if no spawnpoint was available
return None


That code is untested, but I believe it should work.


Awesome, yeah that could work. Would there be a high performance difference if I were to write that code snippet in C++?
My Github repositories:

Source.Python: https://github.com/backraw
User avatar
satoon101
Project Leader
Posts: 2697
Joined: Sat Jul 07, 2012 1:59 am

Postby satoon101 » Thu Jul 23, 2015 3:14 am

I doubt there would be a "high" performance difference, but you would have to run some time tests on it if/when you get that far. To cut "some" time off, you could cast the playeriter_alive_origins to a list prior to either of the loops. That way, you don't have to retrieve the player origins for each spawnpoint that gets checked against.

Syntax: Select all

from random import shuffle


def get_random(self):
"""Return a random spawn-point."""
# Randomize the list by shuffling it
shuffle(self)

# Get a list of alive player origins
origins = list(playeriter_alive_origins)

# Loop through the now randomized spawnpoints
for vector in self:

# Loop through all alive player origins
for origin in origins:

# Is the player too close to this vector?
if vector.get_distance(origin) < DISTANCE_SPAWN:

# Break the player origin loop to move to the next spawnpoint
break

# Was the player origin loop not broken?
else:

# Return the spawnpoint
return vector

# Return None if no spawnpoint was available
return None
Image
User avatar
BackRaw
Senior Member
Posts: 537
Joined: Sun Jul 15, 2012 1:46 am
Location: Germany
Contact:

Postby BackRaw » Fri Jul 24, 2015 5:10 am

Good info. I'll see how far I can make it.

Return to “Plugin Development Support”

Who is online

Users browsing this forum: No registered users and 146 guests