ya2 · news · projects · code · about

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