ya2 · news · projects · code · about

fixes for building
[pmachines.git] / pmachines / app.py
1 import argparse
2 import simplepbr
3 #import gltf
4 from glob import glob
5 from importlib import import_module
6 from inspect import isclass
7 from sys import platform, argv, exit
8 from logging import info, debug
9 from os.path import exists
10 from os import makedirs
11 from multiprocessing import cpu_count
12 from panda3d.core import Filename, load_prc_file_data, AntialiasAttrib, \
13 Texture, WindowProperties, LVector2i, TextNode
14 from panda3d.bullet import BulletWorld, BulletDebugNode
15 from direct.showbase.ShowBase import ShowBase
16 from direct.gui.OnscreenText import OnscreenText
17 from direct.fsm.FSM import FSM
18 from pmachines.audio.music import MusicMgr
19 from pmachines.items.background import Background
20 from pmachines.gui.menu import Menu
21 from pmachines.scene import Scene
22 from pmachines.scenes.scene_basketball import SceneBasketBall
23 from pmachines.scenes.scene_box import SceneBox
24 from pmachines.scenes.scene_domino_box_basketball import SceneDominoBoxBasketball
25 from pmachines.scenes.scene_domino_box import SceneDominoBox
26 from pmachines.scenes.scene_domino import SceneDomino
27 from pmachines.scenes.scene_teeter_domino_box_basketball import SceneTeeterDominoBoxBasketball
28 from pmachines.scenes.scene_teeter_tooter import SceneTeeterTooter
29 from pmachines.posmgr import PositionMgr
30 from ya2.utils.dictfile import DctFile
31 from ya2.p3d.p3d import LibP3d
32 from ya2.utils.lang import LangMgr
33 from ya2.utils.log import LogMgr
34 from ya2.utils.functional import FunctionalTest
35 from ya2.p3d.asserts import assert_threads, assert_tasks, assert_render3d, \
36 assert_render2d, assert_aspect2d, assert_events, assert_buffers
37
38
39 class 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()
50 self.__do_asserts()
51
52 def enterScene(self, cls):
53 self._pmachines.on_scene_enter(cls)
54
55 def exitScene(self):
56 self._pmachines.on_scene_exit()
57 self.__do_asserts()
58
59 def __do_asserts(self):
60 args = self._pmachines._args
61 if True: # args.functional_test or args.functional_ref:
62 assert_threads()
63 assert_tasks()
64 assert_render3d()
65 assert_render2d()
66 assert_aspect2d()
67 assert_events()
68 assert_buffers()
69
70
71 class PmachinesApp:
72
73 scenes = [
74 SceneDomino,
75 SceneBox,
76 SceneDominoBox,
77 SceneBasketBall,
78 SceneDominoBoxBasketball,
79 SceneTeeterTooter,
80 SceneTeeterDominoBoxBasketball]
81
82 def __init__(self):
83 info('platform: %s' % platform)
84 info('exists main.py: %s' % exists('main.py'))
85 self._args = args = self._parse_args()
86 self._configure(args)
87 self.base = ShowBase()
88 self._pipeline = None
89 self.updating = args.update
90 self.version = args.version
91 self.log_mgr = LogMgr.init_cls()()
92 self._pos_mgr = PositionMgr()
93 self._prepare_window(args)
94 if args.update:
95 return
96 if args.functional_test:
97 self._options['settings']['volume'] = 0
98 self._music = MusicMgr(self._options['settings']['volume'])
99 self.lang_mgr = LangMgr(self._options['settings']['language'],
100 'pmachines',
101 'assets/locale/')
102 self._fsm = MainFsm(self)
103 if args.screenshot:
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)
106 scene.screenshot()
107 scene.destroy()
108 exit()
109 elif self._options['development']['auto_start']:
110 mod_name = 'pmachines.scenes.scene_' + self._options['development']['auto_start']
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')
118 if args.functional_test or args.functional_ref:
119 FunctionalTest(args.functional_ref, self._pos_mgr)
120 self.__fps_lst = []
121 taskMgr.do_method_later(1.0, self.__assert_fps, 'assert_fps')
122
123 def on_menu_enter(self):
124 self._menu_bg = Background()
125 self._menu = Menu(
126 self._fsm, self.lang_mgr, self._options, self._music,
127 self._pipeline, self.scenes, self._args.functional_test or self._args.functional_ref,
128 self._pos_mgr)
129
130 def on_home(self):
131 self._fsm.demand('Menu')
132
133 def on_menu_exit(self):
134 self._menu_bg.destroy()
135 self._menu.destroy()
136
137 def on_scene_enter(self, cls):
138 self._set_physics()
139 self._scene = cls(
140 self.world, self.on_home,
141 self._options['development']['auto_close_instructions'],
142 self._options['development']['debug_items'],
143 self.reload,
144 self.scenes,
145 self._pos_mgr,
146 self._args.functional_test or self._args.functional_ref,
147 self._options['development']['mouse_coords'])
148
149 def on_scene_exit(self):
150 self._unset_physics()
151 self._scene.destroy()
152
153 def reload(self, cls):
154 self._fsm.demand('Scene', cls)
155
156 def _configure(self, args):
157 load_prc_file_data('', 'window-title pmachines')
158 load_prc_file_data('', 'framebuffer-srgb true')
159 load_prc_file_data('', 'sync-video true')
160 if args.functional_test or args.functional_ref:
161 load_prc_file_data('', 'win-size 1360 768')
162 # otherwise it is not centered in exwm
163 # load_prc_file_data('', 'threading-model Cull/Draw')
164 # it freezes when you go to the next scene
165 if args.screenshot:
166 load_prc_file_data('', 'window-type offscreen')
167 load_prc_file_data('', 'audio-library-name null')
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')
173 parser.add_argument('--optfile')
174 parser.add_argument('--screenshot')
175 parser.add_argument('--functional-test', action='store_true')
176 parser.add_argument('--functional-ref', action='store_true')
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
181 def _prepare_window(self, args):
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)
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': {
198 'volume': 1,
199 'language': 'en',
200 'fullscreen': 1,
201 'resolution': '',
202 'antialiasing': 1,
203 'shadows': 1},
204 'development': {
205 'simplepbr': 1,
206 'verbose_log': 0,
207 'physics_debug': 0,
208 'auto_start': 0,
209 'auto_close_instructions': 0,
210 'show_buffers': 0,
211 'debug_items': 0,
212 'mouse_coords': 0,
213 'fps': 0}}
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()
221 res = self._options['settings']['resolution']
222 if res:
223 res = LVector2i(*[int(_res) for _res in res.split('x')])
224 else:
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())]
233 res = sorted(resolutions)[-1]
234 fullscreen = self._options['settings']['fullscreen']
235 props = WindowProperties()
236 if args.functional_test or args.functional_ref:
237 fullscreen = False
238 else:
239 props.set_size(res)
240 props.set_fullscreen(fullscreen)
241 props.set_icon_filename('assets/images/icon/pmachines.ico')
242 if not args.screenshot and not self.version:
243 base.win.request_properties(props)
244 #gltf.patch_loader(base.loader)
245 if self._options['development']['simplepbr'] and not self.version:
246 self._pipeline = simplepbr.init(
247 use_normal_maps=True,
248 use_emission_maps=False,
249 use_occlusion_maps=True,
250 msaa_samples=4 if self._options['settings']['antialiasing'] else 1,
251 enable_shadows=int(self._options['settings']['shadows']))
252 debug(f'msaa: {self._pipeline.msaa_samples}')
253 debug(f'shadows: {self._pipeline.enable_shadows}')
254 render.setAntialias(AntialiasAttrib.MAuto)
255 self.base.set_background_color(0, 0, 0, 1)
256 self.base.disable_mouse()
257 if self._options['development']['show_buffers']:
258 base.bufferViewer.toggleEnable()
259 if self._options['development']['fps']:
260 base.set_frame_rate_meter(True)
261 #self.base.accept('window-event', self._on_win_evt)
262 self.base.accept('aspectRatioChanged', self._on_aspect_ratio_changed)
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
274 def _set_physics(self):
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)
281 self._debug_np = render.attach_new_node(debug_node)
282 self._debug_np.show()
283 self.world = BulletWorld()
284 self.world.set_gravity((0, 0, -9.81))
285 if self._options['development']['physics_debug']:
286 self.world.set_debug_node(self._debug_np.node())
287 def update(task):
288 dt = globalClock.get_dt()
289 self.world.do_physics(dt, 10, 1/180)
290 return task.cont
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)
298
299 def _on_aspect_ratio_changed(self):
300 if self._fsm.state == 'Scene':
301 self._scene.on_aspect_ratio_changed()
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:
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)
310 return task.again