ya2 · news · projects · code · about

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