👨🏻‍🔬 Connect With Me

  • 🎯 Seeking Roles: Research or industry roles where I can leverage my multi-disciplinary toolkit, combining low-level systems logic, machine learning workflows, and modern web architecture, to solve real-world problems in engineering, finance, and adjacent fields.
  • 📝 CV / Resume: View my Resume
  • 📍 Location: Bologna, Italy
  • 📱 Phone: (+39) 366 4207296
  • 📧 Email: giovannigravili112@gmail.com
  • 🔗 LinkedIn: linkedin.com/in/giovanni-gravili
  • 🐙 GitHub: github.com/ghovax

Other Projects

🕹️ Networked Game Engine with Entity-Component Architecture

To practice my Python skills, I implemented a proof of concept for a game engine server that exposes a renderer and an entity-component-system (ECS) architecture through a RESTful API. Fundamentally, it manages state and orchestrates concurrent operations across subsystems for rendering, scripting, and networking, separating data (components) from behavior (systems) to keep the codebase modular and extensible. Flask is used for actor control and VisPy for GPU-accelerated rendering, with everything bundled into a monolithic application. As a proof of concept, it implements the essentials: a few API endpoints, one to create an entity, one to add a component to a given entity, and one to remove a given entity.

The motivation behind this project was to understand how game engines handle the separation of concerns between game logic, rendering, and state management. Traditional object-oriented approaches to game development often lead to deeply nested inheritance hierarchies where a Player class might inherit from Character, which inherits from GameObject, which inherits from Renderable, and so forth. This becomes unwieldy as the game grows. The entity-component-system pattern addresses this by treating entities as simple integer identifiers and attaching components to them as needed. An entity that needs to be rendered gets a Renderer component; an entity that needs custom behavior gets a Script component; an entity that has a position in 3D space gets a Transform component. This composition-over-inheritance approach makes it trivial to create new entity types without modifying existing code.

The image shows the addition of an entity: a rotating cube system custom implemented via a server request; it is subsequently removed by a follow-up API call.

Architecture and Threading Model

The server architecture presented several challenges, particularly around threading and state synchronization. Flask runs the HTTP server in one thread while VisPy’s rendering loop executes in the main thread, and these two subsystems must coordinate access to the shared entity state. The solution I implemented uses a global world_lock that guards all operations on the ECS database. Whenever the API creates, modifies, or removes an entity, it acquires this lock; similarly, the rendering loop acquires the lock when iterating over entities to render them. This prevents race conditions where, for instance, the renderer might attempt to read a component from an entity that the API is currently deleting.

In addition to the basic Flask server, I integrated Flask-SocketIO to enable WebSocket communication for real-time updates. This allows clients to subscribe to entity state changes and receive notifications immediately rather than polling the HTTP endpoints. The WebSocket handlers use the same locking mechanism to ensure thread safety. For example, the request_status WebSocket handler acquires the world lock before checking the server state and emitting a response.

Component System and Script Execution

The component system is built on top of the esper ECS library, which provides the low-level primitives for creating entities and attaching components. I defined three primary component types: Transform for position and scale, Script for custom Python scripts, and Renderer for 3D mesh data. Each component is a simple dataclass with no behavior; all logic lives in the API handlers and the script execution system.

The Script component is particularly interesting because it allows arbitrary Python code to be attached to entities and executed at specific lifecycle points. When a script is added to an entity, the server dynamically loads the Python file and checks for two special functions: on_load, which runs once when the script is attached, and on_update, which runs every frame at 60 FPS. The on_update function receives a timer event and can modify the entity’s components, including its transform. To implement this, I use VisPy’s Timer class to schedule the callback and store a reference to both the timer and the script module in a global list. This design enables behavior like rotating a cube or moving an entity along a path to be defined entirely in user-provided Python scripts without modifying the engine code.

One challenge I encountered was ensuring that transformations applied via the API are correctly propagated to the VisPy mesh objects. When a Transform component is added or modified, the corresponding mesh must be updated on the next frame. To handle this, I maintain a global meshes list where each entry contains the entity ID, the file path, the VisPy mesh object, and a toBeTransformed flag. The script’s on_update function checks this flag and, if set, applies the transformation to the mesh and clears the flag. This deferred update pattern prevents the API thread from directly manipulating VisPy objects, which would violate thread safety since VisPy expects all rendering operations to occur on the main thread.

3D Rendering with VisPy

For rendering, I chose VisPy, a Python library that provides GPU-accelerated visualization using OpenGL. VisPy’s scene graph abstraction makes it straightforward to load 3D models and apply transformations. When a Renderer component is added to an entity, the server reads the mesh file using VisPy’s io.read_mesh function, which supports formats like OBJ, FBX, and GLTF. The vertices, faces, and normals are then used to create a Mesh visual, which is added to the VisPy scene as a child of the view.

Each mesh is associated with a MatrixTransform, which allows position, rotation, and scale to be applied. When the Transform component is updated, the mesh’s transformation matrix is recomputed by calling translate and scale methods on the transform object. This approach is efficient because VisPy batches these operations and applies them in a single draw call during the rendering loop.

Lessons and Challenges

Throughout this project, I learned the importance of explicit synchronization when multiple threads share mutable state. Initially, I attempted to use fine-grained locks for individual components, but this led to deadlocks when one operation needed to modify multiple components atomically. Switching to a single coarse-grained lock simplified the implementation and eliminated the deadlock issues, though it does limit concurrency. For a production game engine, a more sophisticated approach, such as a lock-free data structure or a message-passing architecture, would be necessary to scale to hundreds of concurrent operations.

Another challenge was validate API inputs to prevent malformed data from corrupting the entity state. For example, the Transform component requires position and scale arrays with exactly three elements, and scale values must be positive. I implemented validation functions for each component type that check these invariants and return meaningful error messages when validation fails. This catches mistakes early and provides a better developer experience when using the API.

The project repository is available at this link, and the code is free to use under a permissive license. While this is a proof of concept rather than a production-ready engine, it demonstrates how ECS architectures can be exposed via HTTP APIs and how Python can be used for both game logic and rendering.