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
1
5
import random
6
+ import shutil
7
+ import tempfile
8
+ import uuid
2
9
import warnings
3
10
4
11
import axelrod as axl
5
12
from axelrod .interaction_utils import compute_final_score
6
13
from axelrod .action import Action
7
- from ctypes import cdll , c_int , c_float , byref , POINTER
8
14
from .strategies import characteristics
9
15
10
16
C , D = Action .C , Action .D
11
17
actions = {0 : C , 1 : D }
12
18
original_actions = {C : 0 , D : 1 }
13
19
14
20
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
+
15
108
class Player (axl .Player ):
16
109
17
110
classifier = {"stochastic" : True }
111
+ library_manager = None
18
112
19
113
def __init__ (self , original_name ,
20
114
shared_library_name = 'libstrategies.so' ):
@@ -27,9 +121,11 @@ def __init__(self, original_name,
27
121
game: axelrod.Game
28
122
A instance of an axelrod Game
29
123
"""
124
+ if not Player .library_manager :
125
+ Player .library_manager = LibraryManager (shared_library_name )
30
126
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 )
33
129
self .original_name = original_name
34
130
self .original_function = self .original_name
35
131
is_stochastic = characteristics [self .original_name ]['stochastic' ]
@@ -75,17 +171,8 @@ def original_strategy(
75
171
return self .original_function (* [byref (arg ) for arg in args ])
76
172
77
173
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 )
89
176
90
177
if not self .history :
91
178
their_last_move = 0
@@ -107,5 +194,14 @@ def strategy(self, opponent):
107
194
return actions [original_action ]
108
195
109
196
def reset (self ):
197
+ # Release the library before rest, which regenerates the player.
198
+ self .library_manager .release (self .original_name , self .index )
110
199
super ().reset ()
111
200
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