source: geniuswebcore/geniusweb/protocol/session/mopac/MOPACState.py@ 93

Last change on this file since 93 was 90, checked in by Bart Vastenhouw, 3 years ago

Refactor to help reusing partiesserver.

File size: 10.9 KB
Line 
1from typing import List, Dict, Optional
2
3from geniusweb.actions.Action import Action
4from geniusweb.actions.PartyId import PartyId
5from geniusweb.inform.Agreements import Agreements
6from geniusweb.progress.Progress import Progress
7from geniusweb.progress.ProgressRounds import ProgressRounds
8from geniusweb.protocol.ProtocolException import ProtocolException
9from geniusweb.protocol.session.SessionResult import SessionResult
10from geniusweb.protocol.session.SessionState import SessionState
11from geniusweb.protocol.session.mopac.MOPACSettings import MOPACSettings
12from geniusweb.protocol.session.mopac.PartyStates import PartyStates
13from geniusweb.protocol.session.mopac.phase.OfferPhase import OfferPhase
14from geniusweb.protocol.session.mopac.phase.OptInPhase import OptInPhase
15from geniusweb.protocol.session.mopac.phase.Phase import Phase, PHASE_MAXTIME, \
16 PHASE_MINTIME
17from geniusweb.references.PartyWithProfile import PartyWithProfile
18from geniusweb.utils import val, toTuple
19
20
21class MOPACState (SessionState):
22 '''
23 Keeps track of the current {@link Phase}. Adds initializing stuff and
24 time/deadline checking. This state does not contain connections, this assumes
25 that someone else handles (i.e. {@link NegoProtocol} that and the connections
26 with the parties and that negotiation events are just pumped in from there.
27 <p>
28 This object is a bit tricky. It has two states
29 <ol>
30 <li>The initial state, where phase=null and connections are being made to all
31 parties. At this point, problems can not be handled nicely yet because there
32 are no PartyId's for problematic parties
33 <li>The working state, where all parties are connected and all problems can
34 be connected to an offending party.
35 </ol>
36 '''
37
38# private final Phase phase; // maybe null while initializing
39# private final MOPACSettings settings;
40# private final Map<PartyId, PartyWithProfile> partyprofiles;
41# private final List<Action> actions;
42# private final Progress progress;
43#
44# /**
45# * Creates the initial state from the given settings and progress=null
46# *
47# * @param settings the {@link SAOPSettings}
48# */
49# public MOPACState(MOPACSettings settings) {
50# this(null, Arrays.asList(), null, settings, Collections.emptyMap());
51#
52# }
53
54 def __init__(self, phase:Optional[Phase] ,
55 actions:List[Action] ,
56 progress:Optional[Progress] ,
57 settings:MOPACSettings,
58 partyprofiles:Dict[PartyId, PartyWithProfile] ):
59 '''
60 @param phase The Phase, or null if still initializing. Phase is
61 set something only after we have connections.
62 @param actions the legal actions that have been done in the
63 negotiation. {@link PartyStates#getActions()} is
64 called to collect the actions after the phase is
65 finished.first action in list is the oldest. This
66 will not contain actions that immediately led to an
67 agreement, because {@link PartyStates} removes the
68 actions that led to an agreement. This MUST NOT
69 contain illegal actions. Parties doing illegal
70 actions are killed and the offending action ends up
71 in the stacktrace. Previous actions of crashed
72 parties remain standing and valid.
73 @param progress the {@link Progress} line. can be null if not yet
74 known. null happens because during initialization
75 phase the protocol first add all connections. During
76 this time, parties may already enter the
77 failed/exception state.
78 @param settings the {@link SAOPSettings}
79 @param partyprofiles map with the {@link PartyWithProfile} for connected
80 parties. null is equivalent to an empty map.
81 '''
82 self._phase = phase
83 self._actions = actions
84 self._progress = progress
85 self._settings = settings
86 self._partyprofiles = partyprofiles
87
88 def initPhase(self,newprogress:Progress , now:int) -> "MOPACState" :
89 '''
90 Sets the progress for this session and initial phase. Must be called
91 after all parties have been connected with
92 {@link #with(PartyId, PartyWithProfile)}.
93
94 @param newprogress the initial {@link Progress} typically matching the
95 settings deadline object
96 @param now current time ms since 1970
97
98 @return state with the initial partystates , progress set.
99 '''
100 if self._progress != None or newprogress == None or self._phase != None:
101 raise ValueError(
102 "progress must be null, newprogress must be not null and phase must be INIT")
103
104 partyStates = PartyStates(self._getPowers())
105 firstPhase = OfferPhase(partyStates,
106 now + MOPACState._getAvailablePhaseTime(newprogress, now),
107 self._settings.getVotingEvaluator())
108 return MOPACState(firstPhase, self._actions, newprogress, self._settings,
109 self._partyprofiles)
110
111 def getActions(self)->List[Action]:
112 return list(self._actions)
113
114 def getProgress(self)->Optional[Progress] :
115 return self._progress
116
117 def getAgreements(self)-> Agreements:
118 return val(self._phase).getPartyStates().getAgreements();
119
120 def getSettings(self)->MOPACSettings :
121 return self._settings
122
123 def getPartyProfiles(self)->Dict[PartyId, PartyWithProfile]:
124 return dict(self._partyprofiles)
125
126 def isFinal(self, now:int)->bool:
127 return self._phase != None and val(self._phase).isFinal(now) \
128 and not self.isNewPhasePossible(now)
129
130 def getResult(self)->SessionResult :
131 return SessionResult(self._partyprofiles, self.getAgreements(),
132 {}, None)
133
134 @staticmethod
135 def _getAvailablePhaseTime(aprogress:Progress , now:int) ->int:
136 '''
137 @param progress the Progress that needs to be checked
138 @param now current time ms since 1970
139 @return the max possible duration in ms of a phase considering the
140 progress.
141 '''
142 return min(int(aprogress.getTerminationTime().timestamp()*1000) - now,
143 PHASE_MAXTIME)
144
145 def With(self, id:PartyId , partyprofile:PartyWithProfile ) -> "MOPACState" :
146 '''
147 @param id the new {@link PartyId}
148 @param partyprofile the {@link PartyWithProfile} that is associated with
149 this state
150 @return new {@link MOPACState} with the new party added. This call
151 ignores the progress (does not check isFinal) because we uses
152 this during the setup where the deadline is not yet relevant.
153 '''
154 if self._phase != None:
155 raise ValueError(
156 "Adding connections only allowed while initializing");
157
158 newprofiles = dict(self._partyprofiles)
159 newprofiles[id]= partyprofile
160 return MOPACState(None, self._actions,self._progress, self._settings, newprofiles)
161
162 def WithException(self, e:ProtocolException) ->"MOPACState" :
163 '''
164 @param e the {@link ProtocolException} that occured
165 @return a new state with the error set. You MUST have called
166 {@link #initPhase(Progress, long)} before using this
167 '''
168 return MOPACState(val(self._phase).WithException(e), self._actions, self._progress,
169 self._settings,self._partyprofiles)
170
171 def nextPhase(self, now:int)->"MOPACState" :
172 '''
173 Start the next phase. If new phase is OfferPhase, we increase progress.
174 Actions is reset to empty. does nothing if not
175 {@link #isNewPhasePossible(long)}
176
177 @param now current time
178 @return new {@link MOPACState} with phase initialized for next phase.
179 '''
180 remainingNegoTime = int(val(self._progress).getTerminationTime().timestamp()*1000) - now
181 newphase = val(self._phase).next(now, min(remainingNegoTime, PHASE_MAXTIME))
182
183 return MOPACState(newphase, self._actions,
184 MOPACState._increment(val(self._progress), val(self._phase)),
185 self.getSettings(), self._partyprofiles)
186
187 def isNewPhasePossible(self, now:int)->bool:
188 '''
189 When this is called, all parties should have acted.
190
191 @param now current time
192 @return true if there are still &gt;2 parties active and we have enough
193 time for a new phase.
194 '''
195 # System.out.println("phase=" + phase);
196 newprogress = MOPACState._increment(val(self._progress), val(self._phase))
197 if newprogress.isPastDeadline(now + PHASE_MINTIME):
198 return False
199
200 return len(val(self._phase).getPartyStates().getNegotiatingParties()) >= 2\
201 and self._getAvailablePhaseTime(newprogress,
202 now) > PHASE_MINTIME
203
204 def getPhase(self)->Phase :
205 return val(self._phase)
206
207 def WithAction(self, actor:PartyId, action:Action, now:int)->"MOPACState" :
208 '''
209 Check if action is allowed. Add action to the list of actions. Notice,
210 this does NOT check if we need to step to the next phase, because
211 deciding that is also depending on time-outs.
212
213 @param actor the actor that did this action. Can be used to check if
214 action is valid. NOTICE caller has to make sure the current
215 state is not final. MUST NOT be null.
216 @param action the action that was proposed by actor. MUST NOT be null.
217 @param now the current time in ms since 1970, see
218 {@link System#currentTimeMillis()}
219 @return new {@link MOPACState} with the action checked and registered. If
220 the action is not allowed, the new state may be that the actor is
221 in the exception list.
222 '''
223 return MOPACState(val(self._phase).With(actor, action, now), self._actions, self._progress,
224 self._settings, self._partyprofiles)
225
226 @staticmethod
227 def _increment(aprogress:Progress , aphase:Phase ) ->Progress :
228 '''
229 @param aprogress the progress that might need to be advanced
230 @param aphase the phase
231 @return the next progress. Progress round advances if phase is
232 {@link OptIn}.
233 '''
234 if isinstance(aprogress,ProgressRounds) and isinstance(aphase,OptInPhase):
235 return aprogress.advance()
236 return aprogress
237
238 def _getPowers(self)-> Dict[PartyId, int]:
239 '''
240 @return the power of all parties as set in their parameters, default =1.
241 bad power values (non-integer, or <1) are ignored.
242 '''
243 map:Dict[PartyId,int] = {}
244 for pid in self._partyprofiles:
245 if self._partyprofiles[pid].getParty().getParameters().containsKey("power"):
246 power = self._partyprofiles[pid].getParty().getParameters()\
247 .get("power")
248 if not isinstance(power, int) or power < 1:
249 power = 1
250 else:
251 power=1
252 map[pid]=power
253 return map
254
255 def __repr__(self)->str:
256 return "MOPACState[" + str(self._phase) + "," + str(self._settings)\
257 + "," + str(self._partyprofiles) + "," + str(self._progress) + "]"
258
259 def finishPhase(self)->"MOPACState" :
260 '''
261 @return a wrapped-up state, with all parties doen an action or kicked,
262 and agreements collected
263 '''
264 newphase = val(self._phase).finish()
265 newactions = list(self._actions)
266 newactions=newactions+ newphase.getPartyStates().getActions()
267 return MOPACState(newphase, newactions, self._progress, self._settings,
268 self._partyprofiles)
269
270 def __hash__(self):
271 return hash((tuple(self._actions), toTuple(self._partyprofiles),
272 self._phase, self._progress, self._settings))
273
274 def __eq__(self, other):
275 return isinstance(other, self.__class__) \
276 and self._actions==other._actions\
277 and self._partyprofiles==other._partyprofiles\
278 and self._phase==other._phase\
279 and self._progress==other._progress\
280 and self._settings==other._settings
281
Note: See TracBrowser for help on using the repository browser.