1+ from collections import defaultdict
2+ from ctypes import cdll , c_int , c_float , byref , POINTER
3+ from ctypes .util import find_library
4+ import os
15import random
6+ import shutil
7+ import tempfile
8+ import uuid
29import warnings
310
411import axelrod as axl
512from axelrod .interaction_utils import compute_final_score
613from axelrod .action import Action
7- from ctypes import cdll , c_int , c_float , byref , POINTER
814from .strategies import characteristics
915
1016C , D = Action .C , Action .D
1117actions = {0 : C , 1 : D }
1218original_actions = {C : 0 , D : 1 }
1319
1420
21+ self_interaction_message = """
22+ You are playing a match with the same player against itself. However
23+ axelrod_fortran players share memory. You can initialise another instance of an
24+ Axelrod_fortran player with player.clone().
25+ """
26+
27+
28+ class LibraryManager (object ):
29+ """LibraryManager creates and loads copies of a shared library, which
30+ enables multiple copies of the same strategy to be run without the end user
31+ having to maintain many copies of the shared library.
32+
33+ This works by making a copy of the shared library file and loading it into
34+ memory again. Loading the same file again will return a reference to the
35+ same memory addresses.
36+
37+ Additionally, library manager tracks how many copies of the library have
38+ been loaded, and how many copies there are of each Player, so as to load
39+ only as many copies of the shared library as needed.
40+ """
41+
42+ def __init__ (self , shared_library_name , verbose = False ):
43+ self .shared_library_name = shared_library_name
44+ self .verbose = verbose
45+ self .library_copies = []
46+ self .player_indices = defaultdict (set )
47+ self .player_next = defaultdict (set )
48+ # Generate a random prefix for tempfile generation
49+ self .prefix = str (uuid .uuid4 ())
50+ self .library_path = self .find_shared_library (shared_library_name )
51+
52+ def find_shared_library (self , shared_library_name ):
53+ ## This finds only the relative path to the library, unfortunately.
54+ # reduced_name = shared_library_name.replace("lib", "").replace(".so", "")
55+ # self.library_path = find_library(reduced_name)
56+ # Hard code absolute path for testing purposes.
57+ return "/usr/lib/libstrategies.so"
58+
59+ def load_dll_copy (self ):
60+ # Copy the library file to a new location so we can load the copy.
61+ temp_directory = tempfile .gettempdir ()
62+ copy_number = len (self .library_copies )
63+ new_filename = os .path .join (
64+ temp_directory ,
65+ "{}-{}-{}" .format (
66+ self .prefix ,
67+ str (copy_number ),
68+ self .shared_library_name )
69+ )
70+ if self .verbose :
71+ print ("Loading {}" .format (new_filename ))
72+ shutil .copy2 (self .library_path , new_filename )
73+ shared_library = cdll .LoadLibrary (new_filename )
74+ self .library_copies .append (shared_library )
75+
76+ def next_player_index (self , name ):
77+ """Determine the index of the next free shared library copy to
78+ allocate for the player. If none is available then load another copy."""
79+ # Is there a free index?
80+ if len (self .player_next [name ]) > 0 :
81+ return self .player_next [name ].pop ()
82+ # Do we need to load a new copy?
83+ player_count = len (self .player_indices [name ])
84+ if player_count == len (self .library_copies ):
85+ self .load_dll_copy ()
86+ return player_count
87+ # Find the first unused index
88+ for i in range (len (self .library_copies )):
89+ if i not in self .player_indices [name ]:
90+ return i
91+ raise ValueError ("We shouldn't be here." )
92+
93+ def load_library_for_player (self , name ):
94+ index = self .next_player_index (name )
95+ self .player_indices [name ].add (index )
96+ if self .verbose :
97+ print ("allocating {}" .format (index ))
98+ return index , self .library_copies [index ]
99+
100+ def release (self , name , index ):
101+ """Release the copy of the library so that it can be re-allocated."""
102+ self .player_indices [name ].remove (index )
103+ if self .verbose :
104+ print ("releasing {}" .format (index ))
105+ self .player_next [name ].add (index )
106+
107+
15108class Player (axl .Player ):
16109
17110 classifier = {"stochastic" : True }
111+ library_manager = None
18112
19113 def __init__ (self , original_name ,
20114 shared_library_name = 'libstrategies.so' ):
@@ -27,9 +121,11 @@ def __init__(self, original_name,
27121 game: axelrod.Game
28122 A instance of an axelrod Game
29123 """
124+ if not Player .library_manager :
125+ Player .library_manager = LibraryManager (shared_library_name )
30126 super ().__init__ ()
31- self .shared_library_name = shared_library_name
32- self . shared_library = cdll . LoadLibrary ( shared_library_name )
127+ self .index , self . shared_library = \
128+ self . library_manager . load_library_for_player ( original_name )
33129 self .original_name = original_name
34130 self .original_function = self .original_name
35131 is_stochastic = characteristics [self .original_name ]['stochastic' ]
@@ -75,17 +171,8 @@ def original_strategy(
75171 return self .original_function (* [byref (arg ) for arg in args ])
76172
77173 def strategy (self , opponent ):
78- if type (opponent ) is Player \
79- and (opponent .original_name == self .original_name ) \
80- and (opponent .shared_library_name == self .shared_library_name ):
81-
82- message = """
83- You are playing a match with two copies of the same player.
84- However the axelrod fortran players share memory.
85- You can initialise an instance of an Axelrod_fortran player with a
86- `shared_library_name`
87- variable that points to a copy of the shared library."""
88- warnings .warn (message = message )
174+ if self is opponent :
175+ warnings .warn (message = self_interaction_message )
89176
90177 if not self .history :
91178 their_last_move = 0
@@ -107,5 +194,14 @@ def strategy(self, opponent):
107194 return actions [original_action ]
108195
109196 def reset (self ):
197+ # Release the library before rest, which regenerates the player.
198+ self .library_manager .release (self .original_name , self .index )
110199 super ().reset ()
111200 self .original_function = self .original_name
201+
202+ def __del__ (self ):
203+ # Release the library before deletion.
204+ self .library_manager .release (self .original_name , self .index )
205+
206+ def __repr__ (self ):
207+ return self .original_name
0 commit comments