ya2 · news · projects · code · about

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