# 🐍 Snakia Framework **Snakia** is a modern Python framework for creating applications with Entity-Component-System (ECS) architecture, event system, and reactive programming. Built with performance (maybe) and modularity in mind, Snakia provides a clean API for developing complex applications ranging from games to terminal user interfaces. ## 📋 Table of Contents - [🎯 Roadmap & TODO](#-roadmap--todo) - [🚀 Installation](#-installation) - [🚀 Quick Start](#-quick-start) - [🏗️ Architecture](#️-architecture) - [⚙️ Core](#️-core) - [🎯 ECS System](#-ecs-system) - [📡 Event System (ES)](#-event-system-es) - [🔌 Plugin System](#-plugin-system) - [🎨 TUI System](#-tui-system) - [⚡ Reactive Programming (RX)](#-reactive-programming-rx) - [🛠️ Utilities](#️-utilities) - [🎭 Decorators](#-decorators) - [🏷️ Properties](#-properties) - [🌐 Platform Abstraction](#-platform-abstraction) - [📦 Examples](#-examples) - [🤝 Contributing](#-contributing) - [🆘 Support](#-support) - [📄 License](#-license) ### ✨ Key Features - 🏗️ **ECS Architecture** - Flexible entity-component-system for scalable game/app logic - 📡 **Event System** - Asynchronous event handling with filters and priorities - 🔌 **Plugin System** - Modular plugin architecture for extensibility - 🎨 **TUI Framework** - Rich terminal user interface with reactive widgets - ⚡ **Reactive Programming** - Observable data streams and reactive bindings - 🛠️ **Rich Utilities** - Decorators, properties, platform abstraction, and more - 🎯 **Type Safety** - Full type hints and Pydantic integration > ⚠️ **Experimental Framework** > This framework is currently in **beta/experimental stage**. Not all features are fully implemented, there might be bugs, and the API is subject to change. Use at your own risk! 🚧 ## 🚀 Installation ### Prerequisites - **Python** >= 3.12 - **pip** or **uv** (recommended) package manager ### Install from PyPi (recommended) ```bash pip install snakia ``` ### Install from Source ```bash # Clone the repository git clone https://github.com/RuJect/Snakia.git cd Snakia # Install with pip pip install -e . # Or with uv (recommended) uv sync ``` ## 🎯 Roadmap & TODO Here's what we're working on to make Snakia even better: - [ ] Plugin Isolation: restrict plugin access to only events and components statically defined in manifest - [ ] Async & Multithreading: implement proper async/await support and multithreading capabilities - [ ] Platform Support: expand platform abstraction to support more operating systems - [ ] Random Implementations: add various random generations implementations - [ ] TUI Widgets: create more ready-to-use TUI widgets and components - [ ] Code Documentation: add comprehensive docstrings and inline comments - [ ] Documentation: create detailed API documentation and tutorials ## 🚀 Quick Start ```python from snakia.core.engine import Engine from snakia.core.loader import Meta, Plugin, PluginProcessor from snakia.core.ecs import Component from snakia.types import Version # Creating a component class HealthComponent(Component): value: int = 100 max_value: int = 100 # Creating a processor class HealthProcessor(PluginProcessor): def process(self, system): for entity, (health,) in system.get_components(HealthComponent): if health.value <= 0: print(f"Entity {entity} died!") # Creating a plugin class HealthPlugin(Plugin, meta=Meta( name="health", version=Version.from_args(1, 0, 0), processors=(HealthProcessor,) )): def on_load(self): pass def on_unload(self): pass # Starting the engine engine = Engine() engine.loader.register(HealthPlugin) engine.loader.load_all() engine.start() ``` ## 🏗️ Architecture Snakia is built on a modular architecture with clear separation of concerns: ```plaintext Snakia/ ├── core/ # Framework core │ ├── engine.py # Main engine │ ├── ecs/ # Entity-Component-System │ ├── es/ # Event System │ ├── loader/ # Plugin loading system │ ├── rx/ # Reactive programming │ └── tui/ # Terminal User Interface ├── decorators/ # Decorators ├── property/ # Property system ├── platform/ # Platform abstraction ├── utils/ # Utilities ├── random/ # Random number generation ├── field/ # Typed fields └── types/ # Special types ``` ## ⚙️ Core ### Engine The central component of the framework that coordinates all systems: ```python from snakia.core.engine import Engine engine = Engine() # Systems: # - engine.system - ECS system # - engine.dispatcher - Event system # - engine.loader - Plugin loader engine.start() # Start all systems engine.stop() # Stop all systems engine.update() # Update systems ``` ## 🎯 ECS System Entity-Component-System architecture for creating flexible and performant applications. ### Component Base class for all components: ```python from snakia.core.ecs import Component from pydantic import Field class PositionComponent(Component): x: float = Field(default=0.0) y: float = Field(default=0.0) class VelocityComponent(Component): vx: float = Field(default=0.0) vy: float = Field(default=0.0) ``` ### Processor Processors handle components in the system: ```python from snakia.core.ecs import Processor, System class MovementProcessor(Processor): def process(self, system: System) -> None: # Get all entities with Position and Velocity for entity, (pos, vel) in system.get_components( PositionComponent, VelocityComponent ): pos.x += vel.vx pos.y += vel.vy ``` ### System Entity and component management: ```python # Creating an entity with components entity = system.create_entity( PositionComponent(x=10, y=20), VelocityComponent(vx=1, vy=0) ) # Adding a component to an existing entity system.add_component(entity, HealthComponent(value=100)) # Getting entity components pos, vel = system.get_components_of_entity( entity, PositionComponent, VelocityComponent ) # Checking for components if system.has_components(entity, PositionComponent, VelocityComponent): print("Entity has position and velocity") # Removing a component system.remove_component(entity, VelocityComponent) # Deleting an entity system.delete_entity(entity) ``` ## 📡 Event System (ES) Asynchronous event system with filter and priority support. ### Event Base class for events: ```python from snakia.core.es import Event from pydantic import Field class PlayerDiedEvent(Event): player_id: int = Field() cause: str = Field(default="unknown") ttl: int = Field(default=10) # Event lifetime ``` ### Handler Event handlers: ```python from snakia.core.es import Handler, Action def on_player_died(event: PlayerDiedEvent) -> Action | None: print(f"Player {event.player_id} died from {event.cause}") return Action.move(1) # Move to next handler ``` ### Filter Event filters: ```python from snakia.core.es import Filter def only_important_deaths(event: PlayerDiedEvent) -> bool: return event.cause in ["boss", "pvp"] # Using a filter @dispatcher.on(PlayerDiedEvent, filter=only_important_deaths) def handle_important_death(event: PlayerDiedEvent): print("Important death occurred!") ``` ### Dispatcher Central event dispatcher: ```python from snakia.core.es import Dispatcher, Subscriber dispatcher = Dispatcher() # Subscribing to an event dispatcher.subscribe(PlayerDiedEvent, Subscriber( handler=on_player_died, filter=only_important_deaths, priority=10 )) # Decorator for subscription @dispatcher.on(PlayerDiedEvent, priority=5) def handle_death(event: PlayerDiedEvent): print("Death handled!") # Publishing an event dispatcher.publish(PlayerDiedEvent(player_id=123, cause="boss")) ``` ## 🔌 Plugin System Modular system for loading and managing plugins. ### Plugin Base class for plugins: ```python from snakia.core.loader import Meta, Plugin, PluginProcessor from snakia.types import Version class MyProcessor(PluginProcessor): def process(self, system): # Processor logic pass class MyPlugin(Plugin, meta=Meta( name="my_plugin", author="developer", version=Version.from_args(1, 0, 0), processors=(MyProcessor,), subscribers=() )): def on_load(self): print("Plugin loaded!") def on_unload(self): print("Plugin unloaded!") ``` ### Meta Plugin metadata: ```python from snakia.core.loader import Meta from snakia.core.es import Subscriber meta = Meta( name="plugin_name", author="author_name", version=Version.from_args(1, 0, 0), processors=(Processor1, Processor2), subscribers=( (EventType, Subscriber(handler, filter, priority)), ) ) ``` ### Loader Plugin loader: ```python from snakia.core.loader import Loader loader = Loader(engine) # Registering a plugin loader.register(MyPlugin) # Loading all plugins loader.load_all() # Unloading all plugins loader.unload_all() ``` ## 🎨 TUI System System for creating text-based user interfaces. ### Widget Base class for widgets: ```python from snakia.core.tui import Widget, Canvas, CanvasChar from snakia.core.rx import Bindable class MyWidget(Widget): def __init__(self): super().__init__() self.text = self.state("Hello World") self.color = self.state(CanvasChar(fg_color="red")) def on_render(self) -> Canvas: canvas = Canvas(20, 5) canvas.draw_text(0, 0, self.text.value, self.color.value) return canvas ``` ### Canvas Drawing canvas: ```python from snakia.core.tui import Canvas, CanvasChar canvas = Canvas(80, 24) # Drawing text canvas.draw_text(10, 5, "Hello", CanvasChar(fg_color="blue")) # Drawing rectangle canvas.draw_rect(0, 0, 20, 10, CanvasChar("█", fg_color="green")) # Filling area canvas.draw_filled_rect(5, 5, 10, 5, CanvasChar(" ", bg_color="red")) # Lines canvas.draw_line_h(0, 0, 20, CanvasChar("-")) canvas.draw_line_v(0, 0, 10, CanvasChar("|")) ``` ### CanvasChar Character with attributes: ```python from snakia.core.tui import CanvasChar char = CanvasChar( char="A", fg_color="red", # Text color bg_color="blue", # Background color bold=True, # Bold italic=False, # Italic underline=True # Underline ) ``` ### Renderer Screen rendering: ```python from snakia.core.tui import RenderContext from snakia.core.tui.render import ANSIRenderer import sys class StdoutTarget: def write(self, text: str): sys.stdout.write(text) def flush(self): sys.stdout.flush() renderer = ANSIRenderer(StdoutTarget()) with RenderContext(renderer) as ctx: ctx.render(widget.render()) ``` ### Ready-made Widgets ```python from snakia.core.tui.widgets import ( TextWidget, BoxWidget, HorizontalSplitWidget, VerticalSplitWidget ) # Text widget text = TextWidget("Hello", CanvasChar(fg_color="red", bold=True)) # Box widget box = BoxWidget(10, 5, CanvasChar("█", fg_color="yellow")) # Splitters h_split = HorizontalSplitWidget([text1, text2], "|") v_split = VerticalSplitWidget([h_split, box], "-") ``` ## ⚡ Reactive Programming (RX) Reactive programming system for creating responsive interfaces. ### Bindable Reactive variables: ```python from snakia.core.rx import Bindable, ValueChanged # Creating a reactive variable counter = Bindable(0) # Subscribing to changes def on_change(event: ValueChanged[int]): print(f"Counter changed from {event.old_value} to {event.new_value}") counter.subscribe(on_change) # Changing value counter.set(5) # Will call on_change counter(10) # Alternative syntax ``` ### AsyncBindable Asynchronous reactive variables: ```python from snakia.core.rx import AsyncBindable async_counter = AsyncBindable(0) async def async_handler(event: ValueChanged[int]): print(f"Async counter: {event.new_value}") await async_counter.subscribe(async_handler, run_now=True) await async_counter.set(42) ``` ### Operators ```python from snakia.core.rx import map, filter, combine, merge # Transformation doubled = map(counter, lambda x: x * 2) # Filtering even_only = filter(counter, lambda x: x % 2 == 0) # Combining combined = combine(counter, doubled, lambda a, b: a + b) # Merging streams merged = merge(counter, async_counter) ``` ## 🛠️ Utilities ### to_async Converting synchronous functions to asynchronous: ```python from snakia.utils import to_async def sync_function(x): return x * 2 async_function = to_async(sync_function) result = await async_function(5) ``` ### nolock Performance optimization: ```python from snakia.utils import nolock def busy_loop(): while running: # Work nolock() # Release GIL ``` ### inherit Simplified inheritance: ```python from snakia.utils import inherit class Base: def method(self): pass class Derived(inherit(Base)): def method(self): super().method() # Additional logic ``` ### this Reference to current object: ```python from snakia.utils import this def func(): return this() # Returns `` ``` ### throw Throwing exceptions: ```python from snakia.utils import throw def validate(value): if value < 0: throw(ValueError("Value must be positive")) ``` ### frame Working with frames: ```python from snakia.utils import frame def process_frame(): current_frame = frame() # Process frame ``` ## 🎭 Decorators ### inject_replace Method replacement: ```python from snakia.decorators import inject_replace class Original: def method(self): return "original" @inject_replace(Original, "method") def new_method(self): return "replaced" ``` ### inject_before / inject_after Hooks before and after execution: ```python from snakia.decorators import inject_before, inject_after @inject_before(MyClass, "method") def before_hook(self): print("Before method") @inject_after(MyClass, "method") def after_hook(self): print("After method") ``` ### singleton Singleton pattern: ```python from snakia.decorators import singleton @singleton class Database: def __init__(self): self.connection = "connected" ``` ### pass_exceptions Exception handling: ```python from snakia.decorators import pass_exceptions @pass_exceptions(ValueError, TypeError) def risky_function(): # Code that might throw exceptions pass ``` ## 🏷️ Properties ### readonly Read-only property: ```python from snakia.property import readonly class Currency: @readonly def rate(self) -> int: return 100 currency = Currency() currency.rate = 200 print(currency.rate) # Output: 100 ``` ### initonly Initialization-only property: ```python from snakia.property import initonly class Person: name = initonly[str]("name") bob = Person() bob.name = "Bob" print(bob.name) # Output: "Bob" bob.name = "not bob" print(bob.name) # Output: "Bob" ``` ### 🏛️ classproperty Class property: ```python from snakia.property import classproperty class MyClass: @classproperty def class_value(cls): return "class_value" ``` ## 🌐 Platform Abstraction ### 🖥️ PlatformOS Operating system abstraction: ```python from snakia.platform import PlatformOS, OS # Detecting current OS current_os = OS.current() if current_os == PlatformOS.LINUX: print("Running on Linux") elif current_os == PlatformOS.ANDROID: print("Running on Android") ``` ### 🏗️ PlatformLayer Platform layers: ```python from snakia.platform import LinuxLayer, AndroidLayer # Linux layer linux_layer = LinuxLayer() # Android layer android_layer = AndroidLayer() ``` ## 📦 Examples ### Health System ```python from snakia.core.engine import Engine from snakia.core.ecs import Component from snakia.core.es import Event from snakia.core.loader import Meta, Plugin, PluginProcessor from snakia.types import Version from pydantic import Field class HealthComponent(Component): max_value: int = Field(default=100, ge=0) value: int = Field(default=100, ge=0) class DamageComponent(Component): damage: int = Field(ge=0) ticks: int = Field(default=1, ge=0) class DeathEvent(Event): entity: int = Field() class HealthProcessor(PluginProcessor): def process(self, system): # Processing damage for entity, (damage, health) in system.get_components( DamageComponent, HealthComponent ): health.value -= damage.damage damage.ticks -= 1 if damage.ticks <= 0: system.remove_component(entity, DamageComponent) if health.value <= 0: system.remove_component(entity, HealthComponent) self.plugin.dispatcher.publish(DeathEvent(entity=entity)) class HealthPlugin(Plugin, meta=Meta( name="health", version=Version.from_args(1, 0, 0), processors=(HealthProcessor,) )): def on_load(self): pass def on_unload(self): pass # Usage engine = Engine() engine.loader.register(HealthPlugin) engine.loader.load_all() # Creating a player player = engine.system.create_entity( HealthComponent(value=100, max_value=100) ) # Dealing damage engine.system.add_component(player, DamageComponent(damage=25, ticks=1)) engine.start() ``` ### TUI Application ```python from snakia.core.tui import CanvasChar, RenderContext from snakia.core.tui.render import ANSIRenderer from snakia.core.tui.widgets import TextWidget, BoxWidget, VerticalSplitWidget import sys class StdoutTarget: def write(self, text: str): sys.stdout.write(text) def flush(self): sys.stdout.flush() def main(): # Creating widgets title = TextWidget("Snakia TUI", CanvasChar(fg_color="cyan", bold=True)) content = TextWidget("Welcome to Snakia!", CanvasChar(fg_color="white")) box = BoxWidget(20, 5, CanvasChar("█", fg_color="green")) # Layout layout = VerticalSplitWidget([title, content, box], "-") # Rendering renderer = ANSIRenderer(StdoutTarget()) with RenderContext(renderer) as ctx: ctx.render(layout.render()) if __name__ == "__main__": main() ``` ## 🤝 Contributing We welcome contributions to Snakia development! Whether you're fixing bugs, adding features, or improving documentation, your help is appreciated. ### How to Contribute 1. **Fork** the repository 2. **Create** a feature branch (`git checkout -b feature/amazing-feature`) 3. **Make** your changes 4. **Add** tests if applicable 5. **Commit** your changes (`git commit -m 'Add amazing feature'`) 6. **Push** to the branch (`git push origin feature/amazing-feature`) 7. **Open** a Pull Request ### Development Guidelines - Add type hints to all new code - Write clear commit messages - Update documentation for new features - Test your changes thoroughly ## 🆘 Support Need help? We're here to assist you! - 🐛 **Bug Reports** - [GitHub Issues](https://github.com/RuJect/Snakia/issues) - 💬 **Community Chat** - [RuJect Community Telegram](https://t.me/RuJect_Community) - 📧 **Direct Contact** - mailto:rus07tam.uwu@gmail.com ## 📄 License See the `LICENSE` file for details.