aakropotkin / cpoke

A Pokemon Go PvP Simulator written in C
GNU General Public License v3.0
16 stars 2 forks source link

Create CFFI Python Bindings #6

Open aakropotkin opened 4 years ago

aakropotkin commented 4 years ago

This will need to be updated periodically as the project grows.

Begin writing Python CFFI files to allow CPoke's shared object files to be used in Python.

Start with simple functions like calculating CP, CPM, and Damage, which do not require many of the structures/types involved in the simulator.

mcoram commented 3 years ago

I got get_cp_from_stats to run. If I get a non-trivial set of things I'll send a pull request. One thing that's not super clear to me right now is a good way to construct the python objects. In c you've defined a lot of static data. Anyway, one step at a time. I guess loading c_store is a priority.

aakropotkin commented 3 years ago

I just pushed a new update that fixes a few issues with the old cstore. There was some issues caused by the addition of Gen 6 that needed patching.

Which static data is problematic? I have generally avoided any statics which are mutable, so I wouldn't expect that passing those to Python would be an issue. But I'm sure that you have more knowledge here than me, do you have an example or a resource that could help me understand the problem more?

mcoram commented 3 years ago

Glad to hear about the update. There may be no problems, I'm just figuring things out slowly; I'll try to reproduce another couple tests and write back if I actually get stuck. Thanks!

aakropotkin commented 3 years ago

Let me know if i can help with anything. I know documentation is spotty

mcoram commented 3 years ago

I made some progress in the pull request mentioned above and then hacked a bit more. Trying to get something from test_naive_ai.c to run. I ran into trouble at several points. Basically these problems mean I'll have to make functions to mirror things you do by data constructors or by macro definitions in your c code. E.g. it still puzzles me how the macros for ai_t p1_ai = def_naive_ai(); boil out to:

  ai_t p1_ai = (ai_t) { .name = "Naive AI", .select_team = naive_ai_select_team,
 .decide_action = naive_ai_decide_action, .init = naive_ai_init, .free = naive_a
i_free, .aux = 
              ((void *)0) 
              };

If you have suggestions for where to put these glue functions or what to prioritize to form a "basis" of sufficient data constructors, I'd be happy to hear.

aakropotkin commented 3 years ago

Those amount to constructors for classes. If you make an AI "abstract" class then Naive AI would implement that class. The function pointers are implementations of member functions for that class.

A rough example might be ( I'm not sure what kind of wrappers you've built out so I'm making guesses ):

from abc import ABC, abstractmethod
from typing import List, Any
from cffi import FFI

class AI( ABC ):

  name = 'Abstract AI'

  @abstractmethod
  def __init__( self, aux : <cdata 'void *'> = None ) :
    self.ai_ref = None

  @abstractmethod
  def __del__( self ) :
    pass

  @abstractmethod
  def select_team( self, our_roster : Roster, their_roster : Roster, store : Store ) -> List[PvPPokemon] :
    pass

  @abstractmethod
  def decide_action( self, decide_p1 : bool, battle : Battle ) -> PvPAction :
    pass

# -------------------------------------------------------------------------------------------- #

class NaiveAI( AI ):

  name = 'Naive AI'

  def __init__( self, aux=None ):
    self.ffi = FFI()
    self.lib = self.ffi.dlopen( 'naive_ai.so' )
    self.ai_ref = self.new( 'ai_t[1]' )
    self.ai_ref[0].name = self.name # This is probably not the right way to do this but w/e
    self.ai_ref[0].select_team = self.ffi.addressof( self.lib, 'naive_ai_select_team' )
    self.ai_ref[0].decide_action = self.ffi.addressof( self.lib, 'naive_ai_decide_action' )
    self.ai_ref[0].init = self.ffi.addressof( self.lib, 'naive_ai_init' )
    self.ai_ref[0].free = self.ffi.addressof( self.lib, 'naive_ai_free' )
    self.ai_ref[0].aux = self.ffi.NULL
    self.lib.naive_ai_init( self.ai_ref, self.ai_ref[0].aux )

  def __del__( self ):
    self.lib.naive_ai_free( self.ai_ref )
    self.ffi.dlclose( self.lib )

  def select_team( self, our_roster, their_roster, store ):
    team = self.ffi.new( 'pvp_pokemon_t[3]' )
    rsl = self.ffi.naive_ai_select_team( our_roster.roster_ref, their_roster.roster_ref, team, store.store_ref, self.aux )
    assert( rsl == self.lib.AI_SUCCESS )
    # Convert `team` to python if you want
    return [ PvPPokemon( team[0] ), PvPPokemon( team[1] ), PvPPokemon( team[2] ) ]

  def decide_action( self, decide_p1, battle ):
    choice = self.ffi.new( 'pvp_action_t[1]' )
    rsl = self.ffi.naive_ai_decide_action( decide_p1, battle.battle_ref, choice, self.aux )
    assert( rsl == self.lib.AI_SUCCESS )
    return PvPAction( choice[0] )

My plan is ultimately to have all AI Implementations be distributed as Shared Objects, so really on the Python side you could just make an AI class that operates on any shared object. The shared object forms would just have ai_select_team, ai_decide_action, ai_free, and ai_init, and ai_name symbols exported, which can be used to construct an instance of an AI.

mcoram commented 3 years ago

So .... more delays. I'm probably going to continue to be slow. Sorry. Happy if others want to try.

Anyway, I'm still stymied by segfaults. One of the simplest, I think is when I try to call cstore_get_move. When this is called in the test everything passes. If I try to replicate that under cffi, I get a segfault inside the HASH_FIND macro. I'm not familiar with the uthash library. Is it possible I'm linking in some way that angers it or failing to initialize the hash associated with CSTORE or ...? I currently link test_cstore.o directly into the -shared library and rely on it's lines to the effect of

#define CSTORE_GLOBAL_STORE
#include "cstore.h"

... at least so far as I understand the code.

Similarly, I give python access to a pointer to CSTORE, with this helper defined in test_cstore.c: const store_t *get_cstore_p() { return &CSTORE; }

Any ideas?