ya2 · news · projects · code · about

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