Skip to content

Boris-Barboris/PySubs

Repository files navigation

PySubs game concept.

1. It's a training project, with a goal to study Python programming language.

2. Opensource.

3. Portable. Currently, PySFML is used as middleware, wich is using OpenGL.
PySFML works with Python3.3 only atm.

4. Main focus on architecture. Gameplay is secondary. Assets are nonexistent.

5. Requirements for engine:
    - interactive development. Game object environment is decoupled from 
    Engine Core, objects are defined in modules that can be recompiled and 
    reloaded in the runtime. World state should be changed accordingly to 
    encorporate new code. That helps with interactively debugging parts of
    the game.
    - time management. Engine Core is module manager and scheduler. Interactive
    pausing, execution flow alteration.
    - ease of debugging. All parts of python code must be debuggable. Debugging
    itself should be handled by external tools of course, and there should be
    little problem to enter it.
    - not terrible performance.
    - 2D graphics support, 3D not needed, Audio questionable.
    - component model for game objects.

6. No formalized modular testing. Instead, interactive debugging 
mini-programs for particular modules.

7. Gameplay concepts milestone 0.1:
    - one submarine. One torpedo type. One civilian surface ship type.
    Sea of square form. Primitive civilian AI, torpedo guidance mechanism,
    simple acoustic sensors. Basic UI.


Modular architecture:

    Module - corresponds to a python module, prone to dynamic
reloading. Element of structural segregation of program code. In Python it has direct
representative in the form of "python module". It's best to understand it as 
variable namespace, wich can be loaded and unloaded in the runtime.
    Common problem in module reloading is leftover objects in foreign namespaces,
wich will stay in memory and keep old module fields, methods etc. Therefore we 
should handle object lifetime carefully. Constructor should be written in a way 
to register object in some kind of dictionary or array, specific to module.
    Module life cycle is handled by EngineCore. onLoad and onUnload functions
should be exported by all modules that want to be reloadable, and it's module's 
responsibility to handle gracefull variable initialization, restoration, destruction.
    engine.Reloadable module is providing class decorator wich will be used in
order to abstract away reloadable types with a lot of instances, referenced from
many places. It is essentially a smart proxy wrapper with transparent attribute
lookup and global object registration mechanisms, needed for underlying object reallocation 
and reinitialization after module reloading.
    

EngineCore scheduling:

    What is typical game loop made of? Handle I/O. Game logic processing.
Rendering (layered scene rendering, UI). Background long-going tasks. 
Background audio. Some tasks are strongly ordered, some are everlasting daemons. 
It's generally a hard task to make such system universally-applicable.
    It's good to notice, that for background tasks it doesn't really matter who
will handle them, since they are executed by OS scheduler's rules anyway, so
there's no real reason to escalate their creation to EngineCore. Instead, we'll
leave this task to game Modules. EngineCore scheduler on
the other hand will orchestrate synchronous parts of Module execution, in simple
FIFO manner.
    FIFO scheduler will perform sequential actions. Clients of
FIFO scheduler will request for integer-identified slot in ordered list of 
schedulable tasks. EngineCore will then iterate over them in ascending order.
    If some of those Modules needs some form of multithreading or background
processing, he is free to spawn whatever he wants on their own. It is expected
to gracefully stop or restart threads on module reload though.

Debugging:

    pdb standard module provides rich functionality, well exposed by Visual
Studio VSPT extension. All required features are present, so no additional effort
is needed from my side.

IO concept:
    
    Before defining rendering scheme, let's define our main I\O concept.
It consists of two input patterns - managed and unmanaged. Managed will be
using strong input focus, and relay window events to input reciever.
Then, if active managed reciever decides to do so, some events get forarded to
unmanaged scope and are handled there without any particular order or priority.
    Managed scope will operate with rectangular input recievers. Screen space will be
separated on rectangular Z-ordered overlapping areas - ManagedInputHandlers. One of them
or none will be focused. Abovementioned mechanics will be handled by InputManager module.

    
Component model:

    Main idea: entities are separated in order to unite common functionality
in one class, and not to replicate abstraction hierarchy of objects needlessly. 
Entity-Component Model also favours out reloadable paradigm. 
Typical game object in component model: GameObject instance, that has 
collection of Component instances - user-derived classes, that customize 
default engine Component behaviour. Components are usualy implementing inversion of 
control pattern, wich means, that their execution is governed by some 
centralized Module via standard callbacks, and not by the user himself. 
Such concept, if softened by overwrriding possibilities,
proved itself to be viable in popular game engines.
    Both base GameObject and Component classes must be implemented by engine
itself. Also, Reloadable module must be aware of the need to reload class 
hierarchies, and not only one classes. For example, if in hierarchy A->B->C
module, wich defined class B was reloaded, on it's reload all instances of classes
B and C must also be reloaded. Reloadable decorator with EngineCore reload
subscription functionality are handling this problem acceptably.

Module reloading:

    When Python is instructed to reload module, it overwrites the module's dict
from new file. All existing instances of types, defined in that module remain.
PySubs engine is using reloadable decorator from module engine.Reloadable.py
It is a proxy pattern implementation, that changes underlying instance when
it's module is reloaded.
    General info about reloadable classes:
    - all references to reloadable object should be indirect: reference 
Reloadable proxy, not instance itself.
    - attribute lookup should be transparent - hence the need in custom
metaclass and getattr overrides
    - super() is not working with reloadable, since decorator dynamically overrides
inheritance hierarchy to keep proxy type only on the top level. If you want
to get to base class method, use explicit 
super(Type._get_cls(), self).<methodname>(params). _get_cls is classmethod, that
returns underlying original type.
    - no parameterless constructors - always one parameter 'proxy', that is a
reference to proxy object, that will hold the instance, that is constructed now.
    - _reload(self, other, proxy) method is used to reload instance from the 
old one, instead of __init__ initializer. If it is not provided, parameterless
__init__(self, proxy) is tried.
    - currently, bound methods point to underlying object, wich is incorrect.
Use _mproxy(method_name) to get method, that binds symbolically via proxy. That
is the way to go when working with events.
    - you should never derive reloadable class by unreloadable one. It's not
handled, it will not work.

About

Simple submarine game written in python

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages