hyperstellar 1.5.6__py3-none-win_amd64.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. hyperstellar/__init__.py +299 -0
  2. hyperstellar/__init__.pyi +1019 -0
  3. hyperstellar/_native/linux-x64/shaders/axis.frag +8 -0
  4. hyperstellar/_native/linux-x64/shaders/axis.vert +13 -0
  5. hyperstellar/_native/linux-x64/shaders/fragShader.frag +27 -0
  6. hyperstellar/_native/linux-x64/shaders/geoShader.geom +69 -0
  7. hyperstellar/_native/linux-x64/shaders/math.comp +2177 -0
  8. hyperstellar/_native/linux-x64/shaders/paint.comp +1214 -0
  9. hyperstellar/_native/linux-x64/shaders/quad.frag +26 -0
  10. hyperstellar/_native/linux-x64/shaders/quad.geom +149 -0
  11. hyperstellar/_native/linux-x64/shaders/quad.vert +39 -0
  12. hyperstellar/_native/linux-x64/shaders/vertexShader.vert +13 -0
  13. hyperstellar/_native/linux-x64/stellar.so +0 -0
  14. hyperstellar/_native/windows-x64/shaders/axis.frag +8 -0
  15. hyperstellar/_native/windows-x64/shaders/axis.vert +13 -0
  16. hyperstellar/_native/windows-x64/shaders/fragShader.frag +27 -0
  17. hyperstellar/_native/windows-x64/shaders/geoShader.geom +69 -0
  18. hyperstellar/_native/windows-x64/shaders/math.comp +2177 -0
  19. hyperstellar/_native/windows-x64/shaders/paint.comp +1214 -0
  20. hyperstellar/_native/windows-x64/shaders/quad.frag +26 -0
  21. hyperstellar/_native/windows-x64/shaders/quad.geom +149 -0
  22. hyperstellar/_native/windows-x64/shaders/quad.vert +39 -0
  23. hyperstellar/_native/windows-x64/shaders/vertexShader.vert +13 -0
  24. hyperstellar/_native/windows-x64/stellar.pyd +0 -0
  25. hyperstellar/debug.py +169 -0
  26. hyperstellar/jit.py +2010 -0
  27. hyperstellar/jit_linter.py +3 -0
  28. hyperstellar/py.typed +0 -0
  29. hyperstellar/shader.py +1356 -0
  30. hyperstellar/typed.py +152 -0
  31. hyperstellar-1.5.6.dist-info/METADATA +470 -0
  32. hyperstellar-1.5.6.dist-info/RECORD +35 -0
  33. hyperstellar-1.5.6.dist-info/WHEEL +5 -0
  34. hyperstellar-1.5.6.dist-info/licenses/LICENSE +24 -0
  35. hyperstellar-1.5.6.dist-info/top_level.txt +1 -0
@@ -0,0 +1,299 @@
1
+ # hyperstellar/src/hyperstellar/__init__.py
2
+ import os
3
+ import sys
4
+ import time
5
+ import platform
6
+ from pathlib import Path
7
+ from typing import List, Dict, Any, Iterator, Optional, Union
8
+
9
+ # Import the decorator function from jit.py (module-level)
10
+ from .jit import script as _script_func
11
+
12
+ __version__ = "1.5.6"
13
+
14
+ # Platform detection
15
+ system = platform.system().lower()
16
+ arch = platform.machine().lower()
17
+
18
+ if system == "windows" and ("amd64" in arch or "x86_64" in arch):
19
+ platform_dir = "windows-x64"
20
+ lib_name = "stellar.pyd"
21
+ elif system == "linux" and ("x86_64" in arch or "amd64" in arch):
22
+ platform_dir = "linux-x64"
23
+ lib_name = "stellar.so"
24
+ elif system == "darwin":
25
+ platform_dir = "macos-x64"
26
+ lib_name = "stellar.so"
27
+ else:
28
+ raise ImportError(f"Unsupported platform: {system} {arch}")
29
+
30
+ # Native module path
31
+ module_dir = Path(__file__).parent / "_native" / platform_dir
32
+ module_path = module_dir / lib_name
33
+
34
+ if not module_path.exists():
35
+ raise ImportError(
36
+ f"Native module not found: {module_path}\n"
37
+ f"Platform detected: {system} {arch}\n"
38
+ f"Expected: {platform_dir}/{lib_name}"
39
+ )
40
+
41
+ # Windows: help loader find DLL dependencies
42
+ if system == "windows":
43
+ dll_dir = str(module_dir)
44
+ os.environ['PATH'] = dll_dir + ';' + os.environ.get('PATH', '')
45
+ try:
46
+ os.add_dll_directory(dll_dir)
47
+ except Exception:
48
+ pass
49
+
50
+ # ----------------------------------------------------------------------
51
+ # Determine if shader cache already exists
52
+ # ----------------------------------------------------------------------
53
+ cache_dir = None
54
+ if system == "windows":
55
+ local_appdata = os.environ.get("LOCALAPPDATA")
56
+ if local_appdata:
57
+ cache_dir = Path(local_appdata) / "hyperstellar" / "cache"
58
+ else: # Linux/macOS
59
+ home = os.environ.get("HOME")
60
+ if home:
61
+ cache_dir = Path(home) / ".cache" / "hyperstellar"
62
+
63
+ has_cache = False
64
+ if cache_dir and cache_dir.exists():
65
+ has_cache = any(cache_dir.glob("*.bin"))
66
+
67
+ if not has_cache:
68
+ sys.stderr.write(
69
+ f"hyperstellar {__version__}: First-time shader compilation may take 30-60 seconds on older GPUs.\n"
70
+ f" Subsequent runs will be instant (binary cache).\n"
71
+ f"Compiling... "
72
+ )
73
+ sys.stderr.flush()
74
+ else:
75
+ sys.stderr.write(f"hyperstellar {__version__}: Loading cached shaders... ")
76
+ sys.stderr.flush()
77
+
78
+ start_time = time.perf_counter()
79
+
80
+ # Import native module
81
+ try:
82
+ sys.modules.pop('stellar', None)
83
+ sys.path.insert(0, str(module_dir))
84
+ import stellar as _stellar_module
85
+ sys.path.pop(0)
86
+
87
+ # Expose everything from the native module
88
+ for attr_name in dir(_stellar_module):
89
+ if not attr_name.startswith('__'):
90
+ globals()[attr_name] = getattr(_stellar_module, attr_name)
91
+
92
+ # Define __all__
93
+ if hasattr(_stellar_module, '__all__'):
94
+ __all__ = _stellar_module.__all__ + ['script', 'EquationPresets']
95
+ else:
96
+ __all__ = ['script', 'EquationPresets']
97
+
98
+ # Attach @sim.script decorator to the Simulation class
99
+ def _script_method(self, mode='object', debug=False):
100
+ def decorator(func):
101
+ return _script_func(self, debug=debug, mode=mode)(func)
102
+ return decorator
103
+
104
+ Simulation.script = _script_method
105
+
106
+ # =====================================================================
107
+ # ITERATION PROTOCOL
108
+ # =====================================================================
109
+ def _sim_iter(self):
110
+ for i in range(self.object_count()):
111
+ yield self.get_object(i)
112
+
113
+ Simulation.__iter__ = _sim_iter
114
+ Simulation.__len__ = lambda self: self.object_count()
115
+
116
+ # =====================================================================
117
+ # CONTEXT MANAGER
118
+ # =====================================================================
119
+ def _sim_enter(self):
120
+ return self
121
+
122
+ def _sim_exit(self, exc_type, exc_val, exc_tb):
123
+ self.cleanup()
124
+ return False
125
+
126
+ Simulation.__enter__ = _sim_enter
127
+ Simulation.__exit__ = _sim_exit
128
+
129
+ # =====================================================================
130
+ # DUPLICATE_OBJECT (uses native get_all_objects, not overwritten)
131
+ # =====================================================================
132
+ def _duplicate_object(self, obj: Union[int, 'ObjectHandle'], **overrides):
133
+ """
134
+ Duplicate an existing object with optional property overrides.
135
+
136
+ Args:
137
+ obj: Object ID (raw index or ObjectHandle)
138
+ **overrides: Any property to override (x, y, vx, vy, mass, etc.)
139
+
140
+ Returns:
141
+ ObjectHandle: Handle to the new object
142
+ """
143
+ state = self.get_object(obj) # accepts both raw int and handle
144
+ kwargs = {
145
+ 'x': state.x, 'y': state.y,
146
+ 'vx': state.vx, 'vy': state.vy,
147
+ 'mass': state.mass, 'charge': state.charge,
148
+ 'rotation': state.rotation,
149
+ 'angular_velocity': state.angular_velocity,
150
+ 'skin': state.skin_type,
151
+ 'size': state.radius,
152
+ 'width': state.width, 'height': state.height,
153
+ 'r': state.r, 'g': state.g, 'b': state.b, 'a': state.a,
154
+ 'polygon_sides': state.polygon_sides
155
+ }
156
+ kwargs.update(overrides)
157
+ return self.add_object(**kwargs)
158
+
159
+ Simulation.duplicate_object = _duplicate_object
160
+
161
+ # =====================================================================
162
+ # EQUATION PRESETS
163
+ # =====================================================================
164
+ class EquationPresets:
165
+ @staticmethod
166
+ def gravity(G=1.0, eps=0.001):
167
+ return f"ax = -{G} * x / (x*x + y*y + {eps})**1.5; ay = -{G} * y / (x*x + y*y + {eps})**1.5"
168
+
169
+ @staticmethod
170
+ def spring(k=1.0):
171
+ return f"ax = -{k} * x; ay = -{k} * y"
172
+
173
+ @staticmethod
174
+ def damped_spring(k=1.0, damping=0.1):
175
+ return f"ax = -{k} * x - {damping} * vx; ay = -{k} * y - {damping} * vy"
176
+
177
+ @staticmethod
178
+ def orbit(G=1.0, eps=0.001):
179
+ # Note: this function does NOT accept a 'target' argument; target is handled in set_equation_named.
180
+ return (f"dx = target.x - x; dy = target.y - y; r2 = dx*dx + dy*dy + {eps}; "
181
+ f"r = sqrt(r2); ax = {G} * target.mass * dx / (r * r2); ay = {G} * target.mass * dy / (r * r2)")
182
+
183
+ globals()['EquationPresets'] = EquationPresets
184
+
185
+ # =====================================================================
186
+ # OBJECT TAGS/GROUPS (handle‑only, no raw indices)
187
+ # =====================================================================
188
+ class _ObjectTags:
189
+ """
190
+ Lightweight Python-side object tagging system.
191
+ Only ObjectHandles should be stored – raw indices are not accepted.
192
+ """
193
+ def __init__(self, sim):
194
+ self.sim = sim
195
+ self._tags: Dict[str, List['ObjectHandle']] = {}
196
+
197
+ def add(self, tag: str, obj: 'ObjectHandle'):
198
+ if not isinstance(obj, ObjectHandle):
199
+ raise TypeError("Only ObjectHandle can be tagged; use add_object() to get a handle.")
200
+ if tag not in self._tags:
201
+ self._tags[tag] = []
202
+ if obj not in self._tags[tag]:
203
+ self._tags[tag].append(obj)
204
+
205
+ def remove(self, tag: str, obj: 'ObjectHandle'):
206
+ if tag in self._tags and obj in self._tags[tag]:
207
+ self._tags[tag].remove(obj)
208
+ if not self._tags[tag]:
209
+ del self._tags[tag]
210
+
211
+ def get(self, tag: str) -> List['ObjectHandle']:
212
+ return self._tags.get(tag, []).copy()
213
+
214
+ def clear(self, tag: str = None):
215
+ if tag is None:
216
+ self._tags.clear()
217
+ elif tag in self._tags:
218
+ del self._tags[tag]
219
+
220
+ def __contains__(self, tag: str) -> bool:
221
+ return tag in self._tags
222
+
223
+ def __iter__(self):
224
+ return iter(self._tags.items())
225
+
226
+ def __repr__(self):
227
+ return f"<ObjectTags tags={list(self._tags.keys())}>"
228
+
229
+ def _get_tags(self):
230
+ if not hasattr(self, '_tags_storage'):
231
+ self._tags_storage = _ObjectTags(self)
232
+ return self._tags_storage
233
+
234
+ Simulation.tags = property(_get_tags)
235
+
236
+ # =====================================================================
237
+ # REMOVE TAGGED
238
+ # =====================================================================
239
+ def _remove_tagged(self, tag: str):
240
+ if tag in self.tags:
241
+ handles = self.tags.get(tag)
242
+ if handles:
243
+ self.remove_objects(handles)
244
+ self.tags.clear(tag)
245
+
246
+ Simulation.remove_tagged = _remove_tagged
247
+
248
+ # =====================================================================
249
+ # NAMED EQUATION SETTER (with proper target handling for orbit)
250
+ # =====================================================================
251
+ def _set_equation_named(self, obj, preset_name: str, **kwargs):
252
+ """
253
+ Set a physics equation using a named preset.
254
+
255
+ Args:
256
+ obj: Object ID or ObjectHandle
257
+ preset_name (str): "gravity", "spring", "damped_spring", or "orbit"
258
+ **kwargs: Parameters to substitute. For orbit, pass `target=other_handle`.
259
+ """
260
+ # Extract target early for orbit, since orbit's function signature doesn't accept it.
261
+ target = kwargs.pop('target', None) if preset_name == 'orbit' else None
262
+
263
+ if preset_name == "gravity":
264
+ base = EquationPresets.gravity(**kwargs)
265
+ elif preset_name == "spring":
266
+ base = EquationPresets.spring(**kwargs)
267
+ elif preset_name == "damped_spring":
268
+ base = EquationPresets.damped_spring(**kwargs)
269
+ elif preset_name == "orbit":
270
+ base = EquationPresets.orbit(**kwargs)
271
+ if target is not None:
272
+ # target can be a raw int or a handle; convert to p[index] syntax.
273
+ if hasattr(target, 'slot'): # ObjectHandle
274
+ idx = target.slot
275
+ else:
276
+ idx = int(target) # raw index
277
+ base = base.replace('target', f'p[{idx}]')
278
+ else:
279
+ raise ValueError("orbit preset requires a 'target' object (pass target=handle)")
280
+ else:
281
+ raise ValueError(f"Unknown preset: {preset_name}. Available: gravity, spring, damped_spring, orbit")
282
+
283
+ self.set_equation(obj, base)
284
+
285
+ Simulation.set_equation_named = _set_equation_named
286
+
287
+ except ImportError as e:
288
+ raise ImportError(
289
+ f"Failed to load native module: {e}\n"
290
+ f"Module path: {module_path}\n"
291
+ f"Platform: {system} {arch}"
292
+ )
293
+ except Exception as e:
294
+ raise ImportError(f"Unexpected error loading hyperstellar: {e}")
295
+
296
+ # Print completion time
297
+ elapsed = time.perf_counter() - start_time
298
+ sys.stderr.write(f" done in {elapsed:.1f}s.\n")
299
+ sys.stderr.flush()