ya2 · news · projects · code · about

do not do asserts at runtime
[pmachines.git] / pmachines / app.py
CommitLineData
8ee66edd 1import argparse
4894bb48 2import simplepbr
420ce99a 3#import gltf
63e7aeb2 4from glob import glob
a2a89363
FC
5from importlib import import_module
6from inspect import isclass
63e7aeb2 7from sys import platform, argv, exit
94a18c21 8from logging import info, debug
8ee66edd
FC
9from os.path import exists
10from os import makedirs
df4f85fc 11from multiprocessing import cpu_count
6fff1464 12from panda3d.core import Filename, load_prc_file_data, AntialiasAttrib, \
882c058d 13 Texture, WindowProperties, LVector2i, TextNode
1be87278 14from panda3d.bullet import BulletWorld, BulletDebugNode
8ee66edd 15from direct.showbase.ShowBase import ShowBase
882c058d 16from direct.gui.OnscreenText import OnscreenText
5964572b 17from direct.fsm.FSM import FSM
4586cbf6
FC
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
b35b1f62
FC
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
1bfdf72a
FC
35from ya2.p3d.asserts import assert_threads, assert_tasks, assert_render3d, \
36 assert_render2d, assert_aspect2d, assert_events, assert_buffers
8ee66edd
FC
37
38
5964572b
FC
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()
1bfdf72a 50 self.__do_asserts()
5964572b 51
8c9bf90e
FC
52 def enterScene(self, cls):
53 self._pmachines.on_scene_enter(cls)
5964572b
FC
54
55 def exitScene(self):
56 self._pmachines.on_scene_exit()
1bfdf72a
FC
57 self.__do_asserts()
58
59 def __do_asserts(self):
60 args = self._pmachines._args
46c16ae5 61 if not LibP3d.runtime() or args.functional_test or args.functional_ref:
1bfdf72a
FC
62 assert_threads()
63 assert_tasks()
64 assert_render3d()
65 assert_render2d()
66 assert_aspect2d()
67 assert_events()
68 assert_buffers()
5964572b
FC
69
70
c8035584 71class PmachinesApp:
8ee66edd 72
6168d0c2
FC
73 scenes = [
74 SceneDomino,
75 SceneBox,
76 SceneDominoBox,
77 SceneBasketBall,
78 SceneDominoBoxBasketball,
79 SceneTeeterTooter,
80 SceneTeeterDominoBoxBasketball]
81
8ee66edd 82 def __init__(self):
8ee66edd
FC
83 info('platform: %s' % platform)
84 info('exists main.py: %s' % exists('main.py'))
e982cdde 85 self._args = args = self._parse_args()
d18f757d
FC
86 self._configure(args)
87 self.base = ShowBase()
8ce16d6c 88 self._pipeline = None
8ee66edd
FC
89 self.updating = args.update
90 self.version = args.version
aed9737a 91 self.log_mgr = LogMgr.init_cls()()
2d1773b1 92 self._pos_mgr = PositionMgr()
8ce16d6c 93 self._prepare_window(args)
8ee66edd
FC
94 if args.update:
95 return
361d3942 96 if args.functional_test:
edeef6f9 97 self._options['settings']['volume'] = 0
e1e44d5c 98 self._music = MusicMgr(self._options['settings']['volume'])
a0b33e12 99 self.lang_mgr = LangMgr(self._options['settings']['language'],
2aaa10d3
FC
100 'pmachines',
101 'assets/locale/')
5964572b 102 self._fsm = MainFsm(self)
a747111f 103 if args.screenshot:
6168d0c2
FC
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)
a747111f
FC
106 scene.screenshot()
107 scene.destroy()
63e7aeb2
FC
108 exit()
109 elif self._options['development']['auto_start']:
4586cbf6 110 mod_name = 'pmachines.scenes.scene_' + self._options['development']['auto_start']
a2a89363
FC
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')
7277b192 118 if args.functional_test or args.functional_ref:
2d1773b1 119 FunctionalTest(args.functional_ref, self._pos_mgr)
46c16ae5
FC
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')
5964572b
FC
123
124 def on_menu_enter(self):
125 self._menu_bg = Background()
a9aba267
FC
126 self._menu = Menu(
127 self._fsm, self.lang_mgr, self._options, self._music,
2d1773b1
FC
128 self._pipeline, self.scenes, self._args.functional_test or self._args.functional_ref,
129 self._pos_mgr)
5964572b
FC
130
131 def on_home(self):
132 self._fsm.demand('Menu')
133
134 def on_menu_exit(self):
135 self._menu_bg.destroy()
4071c6d8 136 self._menu.destroy()
5964572b 137
8c9bf90e 138 def on_scene_enter(self, cls):
1be87278 139 self._set_physics()
8c9bf90e 140 self._scene = cls(
e669403e 141 self.world, self.on_home,
31237524 142 self._options['development']['auto_close_instructions'],
9914cfc9 143 self._options['development']['debug_items'],
6168d0c2 144 self.reload,
2d1773b1 145 self.scenes,
ce302b41 146 self._pos_mgr,
7fa58640
FC
147 self._args.functional_test or self._args.functional_ref,
148 self._options['development']['mouse_coords'])
5964572b
FC
149
150 def on_scene_exit(self):
151 self._unset_physics()
152 self._scene.destroy()
8ee66edd 153
9914cfc9
FC
154 def reload(self, cls):
155 self._fsm.demand('Scene', cls)
156
d18f757d 157 def _configure(self, args):
8ee66edd 158 load_prc_file_data('', 'window-title pmachines')
4894bb48 159 load_prc_file_data('', 'framebuffer-srgb true')
a9e8696e 160 load_prc_file_data('', 'sync-video true')
d982c0a5 161 if args.functional_test or args.functional_ref:
addec9c9 162 load_prc_file_data('', 'win-size 1360 768')
d982c0a5 163 # otherwise it is not centered in exwm
9914cfc9
FC
164 # load_prc_file_data('', 'threading-model Cull/Draw')
165 # it freezes when you go to the next scene
a747111f 166 if args.screenshot:
d18f757d
FC
167 load_prc_file_data('', 'window-type offscreen')
168 load_prc_file_data('', 'audio-library-name null')
8ee66edd
FC
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')
9ba5488b 174 parser.add_argument('--optfile')
a747111f 175 parser.add_argument('--screenshot')
361d3942 176 parser.add_argument('--functional-test', action='store_true')
edeef6f9 177 parser.add_argument('--functional-ref', action='store_true')
8ee66edd
FC
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
9ba5488b 182 def _prepare_window(self, args):
8ee66edd
FC
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)
9ba5488b
FC
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': {
a0b33e12 199 'volume': 1,
6fff1464
FC
200 'language': 'en',
201 'fullscreen': 1,
a9aba267 202 'resolution': '',
5fdf77d0
FC
203 'antialiasing': 1,
204 'shadows': 1},
9ba5488b 205 'development': {
a5dc83f4 206 'simplepbr': 1,
9ba5488b 207 'verbose_log': 0,
e669403e
FC
208 'physics_debug': 0,
209 'auto_start': 0,
210 'auto_close_instructions': 0,
31237524 211 'show_buffers': 0,
b41381b2 212 'debug_items': 0,
882c058d 213 'mouse_coords': 0,
b41381b2 214 'fps': 0}}
9ba5488b
FC
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()
6fff1464
FC
222 res = self._options['settings']['resolution']
223 if res:
224 res = LVector2i(*[int(_res) for _res in res.split('x')])
225 else:
dd32d640
FC
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())]
6fff1464 234 res = sorted(resolutions)[-1]
edeef6f9 235 fullscreen = self._options['settings']['fullscreen']
d982c0a5 236 props = WindowProperties()
edeef6f9 237 if args.functional_test or args.functional_ref:
edeef6f9 238 fullscreen = False
d982c0a5
FC
239 else:
240 props.set_size(res)
edeef6f9 241 props.set_fullscreen(fullscreen)
7e487769 242 props.set_icon_filename('assets/images/icon/pmachines.ico')
8ce16d6c 243 if not args.screenshot and not self.version:
d18f757d 244 base.win.request_properties(props)
420ce99a 245 #gltf.patch_loader(base.loader)
8ce16d6c 246 if self._options['development']['simplepbr'] and not self.version:
a9aba267 247 self._pipeline = simplepbr.init(
a5dc83f4
FC
248 use_normal_maps=True,
249 use_emission_maps=False,
a9aba267 250 use_occlusion_maps=True,
5fdf77d0
FC
251 msaa_samples=4 if self._options['settings']['antialiasing'] else 1,
252 enable_shadows=int(self._options['settings']['shadows']))
94a18c21
FC
253 debug(f'msaa: {self._pipeline.msaa_samples}')
254 debug(f'shadows: {self._pipeline.enable_shadows}')
4894bb48 255 render.setAntialias(AntialiasAttrib.MAuto)
1be87278
FC
256 self.base.set_background_color(0, 0, 0, 1)
257 self.base.disable_mouse()
e669403e
FC
258 if self._options['development']['show_buffers']:
259 base.bufferViewer.toggleEnable()
b41381b2
FC
260 if self._options['development']['fps']:
261 base.set_frame_rate_meter(True)
651713a9
FC
262 #self.base.accept('window-event', self._on_win_evt)
263 self.base.accept('aspectRatioChanged', self._on_aspect_ratio_changed)
882c058d
FC
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
1be87278 275 def _set_physics(self):
9ba5488b
FC
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)
5964572b
FC
282 self._debug_np = render.attach_new_node(debug_node)
283 self._debug_np.show()
1be87278
FC
284 self.world = BulletWorld()
285 self.world.set_gravity((0, 0, -9.81))
9ba5488b 286 if self._options['development']['physics_debug']:
5964572b 287 self.world.set_debug_node(self._debug_np.node())
1be87278
FC
288 def update(task):
289 dt = globalClock.get_dt()
0625cf49 290 self.world.do_physics(dt, 10, 1/180)
1be87278 291 return task.cont
5964572b
FC
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)
651713a9
FC
299
300 def _on_aspect_ratio_changed(self):
5964572b
FC
301 if self._fsm.state == 'Scene':
302 self._scene.on_aspect_ratio_changed()
2ef21fc3
FC
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:
df4f85fc
FC
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)
2ef21fc3 311 return task.again