ya2 · news · projects · code · about

do not do asserts at runtime
[pmachines.git] / pmachines / app.py
... / ...
CommitLineData
1import argparse
2import simplepbr
3#import gltf
4from glob import glob
5from importlib import import_module
6from inspect import isclass
7from sys import platform, argv, exit
8from logging import info, debug
9from os.path import exists
10from os import makedirs
11from multiprocessing import cpu_count
12from panda3d.core import Filename, load_prc_file_data, AntialiasAttrib, \
13 Texture, WindowProperties, LVector2i, TextNode
14from panda3d.bullet import BulletWorld, BulletDebugNode
15from direct.showbase.ShowBase import ShowBase
16from direct.gui.OnscreenText import OnscreenText
17from direct.fsm.FSM import FSM
18from pmachines.audio.music import MusicMgr
19from pmachines.items.background import Background
20from pmachines.gui.menu import Menu
21from pmachines.scene import Scene
22from pmachines.scenes.scene_basketball import SceneBasketBall
23from pmachines.scenes.scene_box import SceneBox
24from pmachines.scenes.scene_domino_box_basketball import SceneDominoBoxBasketball
25from pmachines.scenes.scene_domino_box import SceneDominoBox
26from pmachines.scenes.scene_domino import SceneDomino
27from pmachines.scenes.scene_teeter_domino_box_basketball import SceneTeeterDominoBoxBasketball
28from pmachines.scenes.scene_teeter_tooter import SceneTeeterTooter
29from pmachines.posmgr import PositionMgr
30from ya2.utils.dictfile import DctFile
31from ya2.p3d.p3d import LibP3d
32from ya2.utils.lang import LangMgr
33from ya2.utils.log import LogMgr
34from ya2.utils.functional import FunctionalTest
35from ya2.p3d.asserts import assert_threads, assert_tasks, assert_render3d, \
36 assert_render2d, assert_aspect2d, assert_events, assert_buffers
37
38
39class MainFsm(FSM):
40
41 def __init__(self, pmachines):
42 super().__init__('Main FSM')
43 self._pmachines = pmachines
44
45 def enterMenu(self):
46 self._pmachines.on_menu_enter()
47
48 def exitMenu(self):
49 self._pmachines.on_menu_exit()
50 self.__do_asserts()
51
52 def enterScene(self, cls):
53 self._pmachines.on_scene_enter(cls)
54
55 def exitScene(self):
56 self._pmachines.on_scene_exit()
57 self.__do_asserts()
58
59 def __do_asserts(self):
60 args = self._pmachines._args
61 if not LibP3d.runtime() or args.functional_test or args.functional_ref:
62 assert_threads()
63 assert_tasks()
64 assert_render3d()
65 assert_render2d()
66 assert_aspect2d()
67 assert_events()
68 assert_buffers()
69
70
71class PmachinesApp:
72
73 scenes = [
74 SceneDomino,
75 SceneBox,
76 SceneDominoBox,
77 SceneBasketBall,
78 SceneDominoBoxBasketball,
79 SceneTeeterTooter,
80 SceneTeeterDominoBoxBasketball]
81
82 def __init__(self):
83 info('platform: %s' % platform)
84 info('exists main.py: %s' % exists('main.py'))
85 self._args = args = self._parse_args()
86 self._configure(args)
87 self.base = ShowBase()
88 self._pipeline = None
89 self.updating = args.update
90 self.version = args.version
91 self.log_mgr = LogMgr.init_cls()()
92 self._pos_mgr = PositionMgr()
93 self._prepare_window(args)
94 if args.update:
95 return
96 if args.functional_test:
97 self._options['settings']['volume'] = 0
98 self._music = MusicMgr(self._options['settings']['volume'])
99 self.lang_mgr = LangMgr(self._options['settings']['language'],
100 'pmachines',
101 'assets/locale/')
102 self._fsm = MainFsm(self)
103 if args.screenshot:
104 cls = [cls for cls in self.scenes if cls.__name__ == args.screenshot][0]
105 scene = cls(BulletWorld(), None, True, False, lambda: None, self.scenes)
106 scene.screenshot()
107 scene.destroy()
108 exit()
109 elif self._options['development']['auto_start']:
110 mod_name = 'pmachines.scenes.scene_' + self._options['development']['auto_start']
111 for member in import_module(mod_name).__dict__.values():
112 if isclass(member) and issubclass(member, Scene) and \
113 member != Scene:
114 cls = member
115 self._fsm.demand('Scene', cls)
116 else:
117 self._fsm.demand('Menu')
118 if args.functional_test or args.functional_ref:
119 FunctionalTest(args.functional_ref, self._pos_mgr)
120 if not LibP3d.runtime() or args.functional_test or args.functional_ref:
121 self.__fps_lst = []
122 taskMgr.do_method_later(1.0, self.__assert_fps, 'assert_fps')
123
124 def on_menu_enter(self):
125 self._menu_bg = Background()
126 self._menu = Menu(
127 self._fsm, self.lang_mgr, self._options, self._music,
128 self._pipeline, self.scenes, self._args.functional_test or self._args.functional_ref,
129 self._pos_mgr)
130
131 def on_home(self):
132 self._fsm.demand('Menu')
133
134 def on_menu_exit(self):
135 self._menu_bg.destroy()
136 self._menu.destroy()
137
138 def on_scene_enter(self, cls):
139 self._set_physics()
140 self._scene = cls(
141 self.world, self.on_home,
142 self._options['development']['auto_close_instructions'],
143 self._options['development']['debug_items'],
144 self.reload,
145 self.scenes,
146 self._pos_mgr,
147 self._args.functional_test or self._args.functional_ref,
148 self._options['development']['mouse_coords'])
149
150 def on_scene_exit(self):
151 self._unset_physics()
152 self._scene.destroy()
153
154 def reload(self, cls):
155 self._fsm.demand('Scene', cls)
156
157 def _configure(self, args):
158 load_prc_file_data('', 'window-title pmachines')
159 load_prc_file_data('', 'framebuffer-srgb true')
160 load_prc_file_data('', 'sync-video true')
161 if args.functional_test or args.functional_ref:
162 load_prc_file_data('', 'win-size 1360 768')
163 # otherwise it is not centered in exwm
164 # load_prc_file_data('', 'threading-model Cull/Draw')
165 # it freezes when you go to the next scene
166 if args.screenshot:
167 load_prc_file_data('', 'window-type offscreen')
168 load_prc_file_data('', 'audio-library-name null')
169
170 def _parse_args(self):
171 parser = argparse.ArgumentParser()
172 parser.add_argument('--update', action='store_true')
173 parser.add_argument('--version', action='store_true')
174 parser.add_argument('--optfile')
175 parser.add_argument('--screenshot')
176 parser.add_argument('--functional-test', action='store_true')
177 parser.add_argument('--functional-ref', action='store_true')
178 cmd_line = [arg for arg in iter(argv[1:]) if not arg.startswith('-psn_')]
179 args = parser.parse_args(cmd_line)
180 return args
181
182 def _prepare_window(self, args):
183 data_path = ''
184 if (platform.startswith('win') or platform.startswith('linux')) and (
185 not exists('main.py') or __file__.startswith('/app/bin/')):
186 # it is the deployed version for windows
187 data_path = str(Filename.get_user_appdata_directory()) + '/pmachines'
188 home = '/home/flavio' # we must force this for wine
189 if data_path.startswith('/c/users/') and exists(home + '/.wine/'):
190 data_path = home + '/.wine/drive_' + data_path[1:]
191 info('creating dirs: %s' % data_path)
192 makedirs(data_path, exist_ok=True)
193 optfile = args.optfile if args.optfile else 'options.ini'
194 info('data path: %s' % data_path)
195 info('option file: %s' % optfile)
196 info('fixed path: %s' % LibP3d.fixpath(data_path + '/' + optfile))
197 default_opt = {
198 'settings': {
199 'volume': 1,
200 'language': 'en',
201 'fullscreen': 1,
202 'resolution': '',
203 'antialiasing': 1,
204 'shadows': 1},
205 'development': {
206 'simplepbr': 1,
207 'verbose_log': 0,
208 'physics_debug': 0,
209 'auto_start': 0,
210 'auto_close_instructions': 0,
211 'show_buffers': 0,
212 'debug_items': 0,
213 'mouse_coords': 0,
214 'fps': 0}}
215 opt_path = LibP3d.fixpath(data_path + '/' + optfile) if data_path else optfile
216 opt_exists = exists(opt_path)
217 self._options = DctFile(
218 LibP3d.fixpath(data_path + '/' + optfile) if data_path else optfile,
219 default_opt)
220 if not opt_exists:
221 self._options.store()
222 res = self._options['settings']['resolution']
223 if res:
224 res = LVector2i(*[int(_res) for _res in res.split('x')])
225 else:
226 resolutions = []
227 if not self.version:
228 d_i = base.pipe.get_display_information()
229 def _res(idx):
230 return d_i.get_display_mode_width(idx), \
231 d_i.get_display_mode_height(idx)
232 resolutions = [
233 _res(idx) for idx in range(d_i.get_total_display_modes())]
234 res = sorted(resolutions)[-1]
235 fullscreen = self._options['settings']['fullscreen']
236 props = WindowProperties()
237 if args.functional_test or args.functional_ref:
238 fullscreen = False
239 else:
240 props.set_size(res)
241 props.set_fullscreen(fullscreen)
242 props.set_icon_filename('assets/images/icon/pmachines.ico')
243 if not args.screenshot and not self.version:
244 base.win.request_properties(props)
245 #gltf.patch_loader(base.loader)
246 if self._options['development']['simplepbr'] and not self.version:
247 self._pipeline = simplepbr.init(
248 use_normal_maps=True,
249 use_emission_maps=False,
250 use_occlusion_maps=True,
251 msaa_samples=4 if self._options['settings']['antialiasing'] else 1,
252 enable_shadows=int(self._options['settings']['shadows']))
253 debug(f'msaa: {self._pipeline.msaa_samples}')
254 debug(f'shadows: {self._pipeline.enable_shadows}')
255 render.setAntialias(AntialiasAttrib.MAuto)
256 self.base.set_background_color(0, 0, 0, 1)
257 self.base.disable_mouse()
258 if self._options['development']['show_buffers']:
259 base.bufferViewer.toggleEnable()
260 if self._options['development']['fps']:
261 base.set_frame_rate_meter(True)
262 #self.base.accept('window-event', self._on_win_evt)
263 self.base.accept('aspectRatioChanged', self._on_aspect_ratio_changed)
264 if self._options['development']['mouse_coords']:
265 coords_txt = OnscreenText(
266 '', parent=base.a2dTopRight, scale=0.04,
267 pos=(-.03, -.06), fg=(.9, .9, .9, 1), align=TextNode.A_right)
268 def update_coords(task):
269 txt = '%s %s' % (int(base.win.get_pointer(0).x),
270 int(base.win.get_pointer(0).y))
271 coords_txt['text'] = txt
272 return task.cont
273 taskMgr.add(update_coords, 'update_coords')
274
275 def _set_physics(self):
276 if self._options['development']['physics_debug']:
277 debug_node = BulletDebugNode('Debug')
278 debug_node.show_wireframe(True)
279 debug_node.show_constraints(True)
280 debug_node.show_bounding_boxes(True)
281 debug_node.show_normals(True)
282 self._debug_np = render.attach_new_node(debug_node)
283 self._debug_np.show()
284 self.world = BulletWorld()
285 self.world.set_gravity((0, 0, -9.81))
286 if self._options['development']['physics_debug']:
287 self.world.set_debug_node(self._debug_np.node())
288 def update(task):
289 dt = globalClock.get_dt()
290 self.world.do_physics(dt, 10, 1/180)
291 return task.cont
292 self._phys_tsk = taskMgr.add(update, 'update')
293
294 def _unset_physics(self):
295 if self._options['development']['physics_debug']:
296 self._debug_np.remove_node()
297 self.world = None
298 taskMgr.remove(self._phys_tsk)
299
300 def _on_aspect_ratio_changed(self):
301 if self._fsm.state == 'Scene':
302 self._scene.on_aspect_ratio_changed()
303
304 def __assert_fps(self, task):
305 if len(self.__fps_lst) > 3:
306 self.__fps_lst.pop(0)
307 self.__fps_lst += [globalClock.average_frame_rate]
308 if len(self.__fps_lst) == 4:
309 fps_threshold = 55 if cpu_count() >= 4 else 25
310 assert(any(fps > fps_threshold for fps in self.__fps_lst), 'low fps %s' % self.__fps_lst)
311 return task.again