77
88from app .models .frame import Frame
99from app .models .apps import get_local_frame_apps , get_local_app_path , get_scene_app_id
10+ from app .codegen .drivers_nim import (
11+ DEFAULT_COMPILATION_MODE ,
12+ compilation_mode_uses_shared_libraries ,
13+ )
1014from app .codegen .utils import sanitize_nim_string , natural_keys
1115from app .utils .js_apps import find_js_app_source_key
1216
@@ -36,6 +40,50 @@ def write_scene_nim(frame: Frame, scene: dict) -> str:
3640 return SceneWriter (frame , scene ).write_scene_nim ()
3741
3842
43+ def compiled_frame_scenes (frame : Frame ) -> list [dict ]:
44+ scenes = getattr (frame , "scenes" , None ) or []
45+ compiled_scenes = []
46+ for scene in scenes :
47+ if not isinstance (scene , dict ):
48+ continue
49+ settings = scene .get ("settings" ) or {}
50+ if settings .get ("execution" , "compiled" ) != "interpreted" :
51+ compiled_scenes .append (scene )
52+ return compiled_scenes
53+
54+
55+ def scene_registry_id (scene : dict ) -> str :
56+ return re .sub (r"[^a-zA-Z0-9\-\_]" , "_" , scene .get ("id" , "default" ))
57+
58+
59+ def scene_module_suffix (scene : dict ) -> str :
60+ return re .sub (r"\W+" , "" , scene_registry_id (scene ))
61+
62+
63+ def scene_module_filename (scene : dict ) -> str :
64+ return f"scene_{ scene_module_suffix (scene )} .nim"
65+
66+
67+ def scene_library_filename (scene : dict ) -> str :
68+ return f"scene_{ scene_module_suffix (scene )} .so"
69+
70+
71+ def write_scene_library_nim (scene : dict ) -> str :
72+ scene_module = f"scene_{ scene_module_suffix (scene )} "
73+ return f"""# This file is autogenerated
74+
75+ import scenes/{ scene_module } as sceneModule
76+ import frameos/channels
77+ import frameos/driver_abi
78+
79+ proc frameos_scene_init*(logHook: HostLogProc, sendEventHook: HostSendEventProc) {{.cdecl, exportc, dynlib.}} =
80+ setSharedHostCallbacks(logHook, sendEventHook)
81+
82+ proc frameos_scene_export*(): pointer {{.cdecl, exportc, dynlib.}} =
83+ result = cast[pointer](sceneModule.exportedScene)
84+ """
85+
86+
3987def field_type_to_nim_type (field_type : str , required : bool = True ) -> str :
4088 match field_type :
4189 case 'select' :
@@ -1633,47 +1681,46 @@ def wrap_with_cache(self, node_id: str, value_list: list[str], data: dict):
16331681 return value_list
16341682
16351683
1636- def write_scenes_nim (frame : Frame ) -> str :
1684+ def _scene_registry_rows (frame : Frame ) -> tuple [ list [ str ], list [ str ], dict | None ] :
16371685 rows = []
1638- imports = []
16391686 sceneOptionTuples = []
16401687 default_scene = None
1641- for scene in frame .scenes :
1642- execution = scene .get ("settings" , {}).get ("execution" , "compiled" )
1643- if execution == "interpreted" :
1644- continue
1688+ for scene in compiled_frame_scenes (frame ):
16451689 if scene .get ("default" , False ):
16461690 default_scene = scene
16471691
1648- scene_id = scene .get ("id" , "default" )
1649- scene_id = re .sub (r"[^a-zA-Z0-9\-\_]" , "_" , scene_id )
1650- scene_id_import = re .sub (r"\W+" , "" , scene_id )
1651- imports .append (
1652- f"import scenes/scene_{ scene_id_import } as scene_{ scene_id_import } "
1653- )
1692+ scene_id = scene_registry_id (scene )
16541693 rows .append (
1655- f' result["{ scene_id } ".SceneId] = scene_{ scene_id_import } .exportedScene'
1694+ f' result["{ scene_id } ".SceneId] = scene_{ scene_module_suffix ( scene ) } .exportedScene'
16561695 )
16571696 sceneOptionTuples .append (
1658- f" (\ "{ scene_id } \ " .SceneId, \" { scene .get (' name' , ' Default' ) } \ " ),"
1697+ f' ("{ scene_id } ".SceneId, " { sanitize_nim_string ( scene .get (" name" , " Default" )) } "),'
16591698 )
1699+ return rows , sceneOptionTuples , default_scene
1700+
16601701
1702+ def _default_scene_line (default_scene : dict | None ) -> str :
16611703 default_scene_id = (
16621704 default_scene .get ("id" , None ) if default_scene is not None else None
16631705 )
16641706 if default_scene_id is None :
1665- default_scene_line = "let defaultSceneId* = none(SceneId)"
1666- else :
1667- default_scene_id = re .sub (r"[^a-zA-Z0-9\-\_]" , "_" , default_scene_id )
1668- default_scene_line = f'let defaultSceneId* = some("{ default_scene_id } ".SceneId)'
1707+ return "let defaultSceneId* = none(SceneId)"
1708+ return f'let defaultSceneId* = some("{ scene_registry_id (default_scene )} ".SceneId)'
1709+
16691710
1711+ def write_static_scenes_nim (frame : Frame ) -> str :
1712+ rows , sceneOptionTuples , default_scene = _scene_registry_rows (frame )
1713+ imports = [
1714+ f"import scenes/scene_{ scene_module_suffix (scene )} as scene_{ scene_module_suffix (scene )} "
1715+ for scene in compiled_frame_scenes (frame )
1716+ ]
16701717 newline = "\n "
16711718 scenes_source = f"""
16721719import frameos/types
16731720import tables, options
16741721{ newline .join (sorted (imports ))}
16751722
1676- { default_scene_line }
1723+ { _default_scene_line ( default_scene ) }
16771724
16781725const sceneOptions*: array[{ len (sceneOptionTuples )} , tuple[id: SceneId, name: string]] = [
16791726{ newline .join (sorted (sceneOptionTuples ))}
@@ -1685,3 +1732,117 @@ def write_scenes_nim(frame: Frame) -> str:
16851732"""
16861733
16871734 return scenes_source
1735+
1736+
1737+ def write_shared_scenes_nim (frame : Frame ) -> str :
1738+ compiled_scenes = compiled_frame_scenes (frame )
1739+ sceneOptionTuples = [
1740+ f' ("{ scene_registry_id (scene )} ".SceneId, "{ sanitize_nim_string (scene .get ("name" , "Default" ))} "),'
1741+ for scene in compiled_scenes
1742+ ]
1743+ default_scene = next ((scene for scene in compiled_scenes if scene .get ("default" , False )), None )
1744+ specs = [
1745+ "SceneSpec("
1746+ f'id: "{ scene_registry_id (scene )} ".SceneId, '
1747+ f'name: "{ sanitize_nim_string (scene .get ("name" , "Default" ))} ", '
1748+ f'libraryName: "{ scene_library_filename (scene )} "'
1749+ ")"
1750+ for scene in compiled_scenes
1751+ ]
1752+
1753+ newline = "\n "
1754+ spec_lines = ("," + newline + " " ).join (specs )
1755+ if spec_lines :
1756+ spec_lines = newline + " " + spec_lines + newline
1757+
1758+ scenes_source = f"""
1759+ import std/[dynlib, json, options, os, tables]
1760+ import frameos/types
1761+ import frameos/channels as hostChannels
1762+ import frameos/driver_abi
1763+
1764+ type
1765+ SceneSpec = object
1766+ id: SceneId
1767+ name: string
1768+ libraryName: string
1769+
1770+ LoadedSceneLibrary = object
1771+ spec: SceneSpec
1772+ library: LibHandle
1773+ exportedScene: ExportedScene
1774+
1775+ SceneInitProc = proc(logHook: HostLogProc, sendEventHook: HostSendEventProc) {{.cdecl.}}
1776+ SceneExportProc = proc(): pointer {{.cdecl.}}
1777+
1778+ let sceneSpecs: seq[SceneSpec] = @[{ spec_lines } ]
1779+ var loadedSceneLibraries: seq[LoadedSceneLibrary] = @[]
1780+
1781+ { _default_scene_line (default_scene )}
1782+
1783+ const sceneOptions*: array[{ len (sceneOptionTuples )} , tuple[id: SceneId, name: string]] = [
1784+ { newline .join (sorted (sceneOptionTuples ))}
1785+ ]
1786+
1787+ proc hostLog(event: JsonNode) {{.cdecl, gcsafe.}} =
1788+ hostChannels.log(event)
1789+
1790+ proc hostSendEvent(scene: Option[SceneId], event: string, payload: JsonNode) {{.cdecl, gcsafe.}} =
1791+ hostChannels.sendEvent(scene, event, payload)
1792+
1793+ proc sceneLibraryPath(spec: SceneSpec): string =
1794+ getAppDir() / "scenes" / spec.libraryName
1795+
1796+ proc loadRequiredSymbol[T](library: LibHandle, sceneId: SceneId, symbol: string): T =
1797+ let address = symAddr(library, symbol)
1798+ if address.isNil:
1799+ hostChannels.log(%*{{"event": "scene:shared:error", "sceneId": sceneId.string,
1800+ "error": "Missing symbol", "symbol": symbol}})
1801+ return nil
1802+ cast[T](address)
1803+
1804+ proc loadSharedScene(spec: SceneSpec): Option[ExportedScene] =
1805+ let path = sceneLibraryPath(spec)
1806+ let library = loadLib(path)
1807+ if library.isNil:
1808+ hostChannels.log(%*{{"event": "scene:shared:error", "sceneId": spec.id.string,
1809+ "error": "Unable to load scene library", "path": path}})
1810+ return none(ExportedScene)
1811+
1812+ let initProc = loadRequiredSymbol[SceneInitProc](library, spec.id, "frameos_scene_init")
1813+ if initProc.isNil:
1814+ unloadLib(library)
1815+ return none(ExportedScene)
1816+ initProc(hostLog, hostSendEvent)
1817+
1818+ let exportProc = loadRequiredSymbol[SceneExportProc](library, spec.id, "frameos_scene_export")
1819+ if exportProc.isNil:
1820+ unloadLib(library)
1821+ return none(ExportedScene)
1822+
1823+ let exportedScene = cast[ExportedScene](exportProc())
1824+ if exportedScene.isNil:
1825+ hostChannels.log(%*{{"event": "scene:shared:error", "sceneId": spec.id.string,
1826+ "error": "Scene library returned nil export", "path": path}})
1827+ unloadLib(library)
1828+ return none(ExportedScene)
1829+
1830+ loadedSceneLibraries.add(LoadedSceneLibrary(spec: spec, library: library, exportedScene: exportedScene))
1831+ hostChannels.log(%*{{"event": "scene:shared", "sceneId": spec.id.string, "path": path, "loaded": true}})
1832+ return some(exportedScene)
1833+
1834+ proc getExportedScenes*(): Table[SceneId, ExportedScene] =
1835+ result = initTable[SceneId, ExportedScene]()
1836+ for spec in sceneSpecs:
1837+ let exportedScene = loadSharedScene(spec)
1838+ if exportedScene.isSome:
1839+ result[spec.id] = exportedScene.get()
1840+ """
1841+
1842+ return scenes_source
1843+
1844+
1845+ def write_scenes_nim (frame : Frame , compilation_mode : str = DEFAULT_COMPILATION_MODE ) -> str :
1846+ if compilation_mode_uses_shared_libraries (compilation_mode ):
1847+ return write_shared_scenes_nim (frame )
1848+ return write_static_scenes_nim (frame )
0 commit comments