Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1# -*- coding: utf-8 -*- 

2# TODO: automatic registering url with factory at definition time 

3# TODO: use external YAML configuration for STM and late-binding 

4# TODO: STM definition can be merged using merge_config() 

5# TODO: Dump STM definition to YAML file. 

6# TODO: Check better way to store compile info without replacing serializable definition 

7# TODO: a simple function to add a transition or function to state 

8# TODO: add a layer/stm to an existing working protocol 

9# TODO: protocols are closed when no layer is attached. 

10# TODO: extend the event protocol with target, sender, etc? 

11# TODO: maybe not, isolating many reactors in threads with transports 

12# TODO: and one single supervisor. Seems to be better approach. 

13# TODO: use a external Loop class to handle with transport and timers 

14# TODO: isolate same event group of STM in a reactor. 

15 

16 

17""" 

18STM, Reactor and Loop provide a similar concept as docker, kubernetes, etc 

19but instead of using light-virtualization using container, using 

20logic and group of logic applications that can be packaged, retrieving 

21 

22- [ ] Test Driven (ptw --pdb) and Log approach, try not to debug so much 

23- [ ] Optimize for speed, but friendly user interface 

24- [ ] Register speed as we make changes in code 

25- [ ] Have in mind that this support may be compiled in C++ or any other language 

26 but all the logic, structure, etc must works seamless. 

27- [ ] Check which approach is faster for regular cases: need to code and implement both approaches 

28- [ ] All activity relay on transport (fake or not) and timers. 

29- [ ] select() exclusive approach 

30- [ ] developer user friendly 

31- [ ] if a method does not exist, just ignore (but strict=True) 

32- [ ] when compiling a STM, don't remove textual definition (save STM) 

33- [ ] separate STM definition and implementation, so we can isolated each other 

34- [ ] transport: source, endpoint are named and can be located 

35- [ ] can attach a STM to a protocol at any moment 

36- [ ] None means 'any': for reactors, source, endpoints, protocols 

37- [ ] can attach a STM to a protocol at any moment 

38- [ ] None means 'any': for reactors, source, endpoints 

39- [ ] Isolate logic in Reactors (Apps), and have as many as necessary. 

40- [ ] This may change the philosophy of current design, oriented to a single Reactor\ 

41 with a huge event table. 

42- [ ] Can attach a (STM+Reactor) to a protocol by URL. If is already connected, just attach 

43- [ ] If is not connect, try to connect for 1st time. 

44- [ ] Listen by url, and register one protocol (as factory) to URL 

45- [ ] Add as many STM to the URL, so Loop can instantiate a (STM/Reactor) 

46 when someone connect with us. 

47- [ ] Package format to save App and send remotely. 

48- [ ] Rename Reactor by App 

49- [ ] Layer/STM inventory, search, get, etc, similar to public dockers containers 

50- [ ] STM server inventory, from where to pull STM/APP/SWARM 

51""" 

52import re 

53from time import time 

54from itertools import chain 

55 

56from gutools.tools import _call 

57# -------------------------------------------------- 

58# logger 

59# -------------------------------------------------- 

60from gutools.loggers import logger, trace, debug, info, warn, error, exception 

61log = logger(__name__) 

62 

63# -------------------------------------------------- 

64# Definitions 

65# -------------------------------------------------- 

66STATE_INIT = 'INIT' 

67STATE_READY = 'READY' 

68STATE_END = 'END' 

69EVENT_TERM = '<term>' # term alike 

70EVENT_QUIT = '<quit>' # kill alike, but a chance to do something before die 

71 

72MERGE_ADD = 'add' 

73MERGE_REPLACE_EXISTING = 'replace_existing' 

74MERGE_REPLACE_ALL = 'replace_all' 

75 

76def _merge(states, transitions, mode=MERGE_ADD): 

77 """Merge states and transitions to create hierarchies of layers""" 

78 _states = states.pop(0) if states else dict() 

79 _transitions = transitions.pop(0) if transitions else dict() 

80 

81 if mode in (MERGE_ADD, ): 

82 pass 

83 elif mode in (MERGE_REPLACE_EXISTING, ): 

84 for st in states: 

85 for state, new_functions in st.items(): 

86 _states.pop(state, None) 

87 

88 for tr in transitions: 

89 for source, new_info in tr.items(): 

90 info = _transitions.setdefault(source, dict()) 

91 for event, new_trx in new_info.items(): 

92 trx = info.setdefault(event, list()) 

93 for new_t in new_trx: 

94 for i, t in reversed(list(enumerate(trx))): 

95 if t[:1] == new_t[:1]: 

96 trx.pop(i) 

97 

98 elif mode in (MERGE_REPLACE_ALL, ): 

99 states.clear() 

100 for tr in transitions: 

101 for source, new_info in tr.items(): 

102 _transitions.pop(source, None) 

103 else: 

104 raise RuntimeError(f"Unknown '{mode}' MERGE MODE") 

105 

106 # merge states 

107 for st in states: 

108 for state, new_functions in st.items(): 

109 functions = _states.setdefault(state, list([[], [], []])) 

110 for i, func in enumerate(new_functions): 

111 for f in func: 

112 if f not in functions[i]: 

113 functions[i].append(f) 

114 

115 # merge transitions 

116 for tr in transitions: 

117 for source, new_info in tr.items(): 

118 info = _transitions.setdefault(source, dict()) 

119 for event, new_trx in new_info.items(): 

120 trx = info.setdefault(event, list()) 

121 for new_t in new_trx: 

122 for t in trx: 

123 if t[:2] == new_t[:2]: 

124 t[-1].extend(new_t[-1]) 

125 break 

126 else: 

127 trx.append(new_t) 

128 

129 return _states, _transitions 

130 

131# -------------------------------------------------- 

132# Layer 

133# -------------------------------------------------- 

134class Layer(): 

135 """"Minimal event logic""" 

136 def __init__(self, states=None, transitions=None, context=None): 

137 self.states = states if states is not None else dict() 

138 self.transitions = transitions if transitions is not None else dict() 

139 self.context = context if context is not None else dict() 

140 

141 self.app = None 

142 self.state = None 

143 

144 # Setup layer logic 

145 for name, states, transitions, mode, _ in self._get_layer_setups(): 

146 self._merge(states, transitions, mode) 

147 

148 def _merge(self, states, transitions, mode=MERGE_ADD): 

149 """Merge states and transitions to create hierarchies of layers""" 

150 _merge([self.states, states], [self.transitions, transitions], mode=mode) 

151 

152 def _get_layer_setups(self, include=None, skip=None): 

153 include = include or [] 

154 skip = skip or [] 

155 

156 match = re.compile('_setup_(?P<name>.*)').match 

157 names = [name for name in dir(self) if match(name)] 

158 names.sort() 

159 for name in names: 

160 logic = match(name).groupdict()['name'] 

161 

162 if logic in skip: 

163 continue 

164 if include and logic not in include: 

165 continue 

166 

167 func = getattr(self, name) 

168 states, transitions, mode = _call(func, **self.context) 

169 yield logic, states, transitions, mode, func.__doc__ 

170 

171 # ----------------------------------------------- 

172 # Layer definitions 

173 # ----------------------------------------------- 

174 def _setup_term(self): 

175 """Set TERM and QUIT logic for the base layer.""" 

176 states = { 

177 STATE_INIT: [[], [], ['start']], 

178 STATE_READY: [[], [], []], 

179 STATE_END: [[], ['bye'], []], 

180 } 

181 transitions = { 

182 STATE_INIT: { 

183 None: [ 

184 [STATE_READY, [], []], 

185 ], 

186 }, 

187 STATE_READY: { 

188 EVENT_TERM: [ 

189 [STATE_READY, [], ['term']], 

190 ], 

191 EVENT_QUIT: [ 

192 [STATE_END, [], ['quit']], 

193 ], 

194 }, 

195 STATE_END: { 

196 }, 

197 } 

198 return states, transitions, MERGE_ADD 

199 

200 

201# -------------------------------------------------- 

202# STM 

203# -------------------------------------------------- 

204class STM(): 

205 """Shared context for some layers""" 

206 

207# -------------------------------------------------- 

208# App 

209# -------------------------------------------------- 

210 

211class App(): 

212 """A Layer / STM container that represent a minimal application""" 

213 def __init__(self, loop): 

214 self.loop = None 

215 self.layers = dict() 

216 loop.attach(self) 

217 

218# -------------------------------------------------- 

219# Loop 

220# -------------------------------------------------- 

221 

222class Loop(): 

223 """The reactor loop""" 

224 def __init__(self): 

225 self._ready_transitions = dict() # The events that can be processed at this time 

226 self.running = False 

227 self.t0 = 0 

228 

229 def attach(self, logic): 

230 """Attach a Layer or App to loop""" 

231 if isinstance(logic, App): 

232 for layer in logic.layers: 

233 self.attach(layer) 

234 elif isinstance(logic, Layer): 

235 foo = 1 

236 

237 logic.loop = self 

238 

239 def run(self): 

240 """Main loop 

241  

242 while is running: 

243 - get next event: [timer, None, tramsport] 

244 - check preconditions 

245 -  

246  

247 """ 

248 self.t0 = time() 

249 self.running = True 

250 while self.running: 

251 event = self.next_event() # blocking 

252 info = self._ready_transitions.get(event, []) 

253 

254 # iterate over all ready transitions 

255 for layer, transitions in chain(info.items()): 

256 ctx = layer.context 

257 ctx['key'] = key 

258 ctx['data'] = data 

259 # to allow callbacks receive the params directly 

260 isinstance(data, dict) and ctx.update(data) 

261 

262 # try to execute the transition if preconditions are True 

263 # func_names are not used, but we want to keep for serialization 

264 for (new_state, preconds, func_names, func_methods, comp_preconds) in transitions: 

265 try: 

266 # Check preconditions 

267 for pre in comp_preconds: 

268 if not eval(pre, ctx): 

269 break 

270 else: 

271 # all preconditions are True or missing 

272 if new_state != layer.state: # fast self-transitions 

273 # remove current state events 

274 for ev in layer.transitions[layer.state]: 

275 self.events[ev].pop(layer) # must exists!! 

276 # add new state events 

277 for ev, trx in layer.transitions[new_state].items(): 

278 self.events[ev][layer] = trx 

279 

280 # execute EXIT state functions (old state) 

281 for func in layer.states[layer.state][GROUP_EXIT]: 

282 func(**ctx) 

283 

284 # execute transition functions 

285 for func in func_methods: 

286 func(**ctx) 

287 

288 layer.state = new_state 

289 

290 # execute ENTRY state functions (new state) 

291 for func in layer.states[new_state][GROUP_ENTRY]: 

292 func(**ctx) 

293 

294 # execute DO state functions (new state) 

295 for func in layer.states[new_state][GROUP_DO]: 

296 func(**ctx) 

297 

298 break # assume there's only 1 transition possible each time 

299 

300 except Exception as why: 

301 exception() 

302 #print() 

303 #print(f"- Reactor {'-'*70}") 

304 #print(f"*** ERROR: {why} ***") 

305 #traceback.print_exc() 

306 #print("-" * 80) 

307 foo = 1 

308 

309 pass 

310 

311