ya2 · news · projects · code · about

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