Skip to content

Commit

Permalink
move can import and threading into py packages, gut can import since …
Browse files Browse the repository at this point in the history
…its not as needed now
  • Loading branch information
ThePotatoGuy committed Feb 25, 2024
1 parent a0a326f commit 0e9fbac
Show file tree
Hide file tree
Showing 8 changed files with 400 additions and 624 deletions.
468 changes: 12 additions & 456 deletions Monika After Story/game/0imports.rpy

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions Monika After Story/game/python-packages/mas/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@


Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@

from .masimport import MASImport
from .exports import MASImport_ssl, MASImport_certifi
36 changes: 36 additions & 0 deletions Monika After Story/game/python-packages/mas/can_import/exports.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@


from .masimport import MASImport
from ..renpyspec import Renpy


class MASImport_certifi(MASImport):
"""
certifi import
"""

def __init__(self):
"""
Constructor
"""
super().__init__("certifi")

def import_try(self, renpy: Renpy = None):
import certifi
return True


class MASImport_ssl(MASImport):
"""
SSL Import
"""

def __init__(self):
"""
Constructor
"""
super().__init__("ssl")

def import_try(self, renpy: Renpy = None):
import ssl
return True
174 changes: 174 additions & 0 deletions Monika After Story/game/python-packages/mas/can_import/masimport.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@

from typing import Dict

from .. renpyspec import Renpy

_imports: Dict[str, 'MASImport'] = {}
# key: module name
# value: MASImport object


def check_imports(renpy: Renpy):
"""
checks import availability
:param renpy: renpy object for import usage
"""
for module_name in _imports:
_imports[module_name].load(renpy)


class MASImport():
"""
Wrapper around import checks for MAS-based imports.
All conditional imports should extend this class and implement all
required functions.
Use this before actually running a third-party import.
All functionality that relies on third-party imports should be capable
of being turned off.
"""
_IMPORT_ERR = "Failed to import `{0}`: {1}"

def __init__(self, module_name: str, set_sys: bool = False):
"""
Constructor
:param module_name: module being imported
:param set_sys: pass True to allow this to overwrite the sys modules.
This should only be used in cases wher eyou need to override a system module.
(Default: False)
"""

self.__module_name = module_name
self.__set_sys = set_sys
self.__enabled = False
self.mas_log = None

# add to imports dict
# crashes if already exists
if module_name in _imports:
raise Exception(
"duplicate import object - {0}".format(module_name)
)

_imports[self.__module_name] = self

def _set_log(self, log):
"""
set the mas log (temp use only)
:param log: log to set mas log to
"""
self.mas_log = log

def __call__(self):
"""
Just returns if this is enabled or not
"""
return self.enabled

@property
def enabled(self):
"""
True if this import is enabled (can be imported)
"""
return self.__enabled

@property
def module_name(self):
"""
module name for this import
"""
return self.__module_name

@property
def set_sys(self):
"""
True if sys module can be overwritten by this.
"""
return self.__set_sys

def log_import_error(self, exp=None):
"""
Logs import error message.
IN:
exp - current exception if available.
(Default: None)
"""
self.mas_log.error(self._IMPORT_ERR.format(
self.module_name,
"" if exp is None else repr(exp)
))

def import_try(self, renpy: Renpy = None):
"""
DERIVED CLASSES MUST IMPLEMENT THIS
This should check if the import should be enabled.
For more info, see `load`.
:param renpy: renpy object for usage by derived classes
:returns: True if the import should be enabled, False otherwise.
"""
raise NotImplementedError

def import_except(self, err, renpy: Renpy = None):
"""
Runs if an ImportError is encountered. The import will always
be disabled and an import error is logged after this runs.
If you need to run additional behavior or set other vars on a
failed import, override this function.
For more info, see `load`.
:param err: the exception that was raised
:param renpy: renpy object for usage by derived classes
"""
pass

def load(self, renpy: Renpy = None):
"""
Loads the import and checks that it works. This is called
sometime before init level -1000.
This will call `import_try` and mark the this import as enabled if
appropriate.
If an ImportError/AttributeError/NameError is triggered,
disable this import, log an import error, and call `import_except.
If any other errors occur, the import will be disabled and
an import error will be logged, but the error will percolate up.
If you wish to catch those errors, override this function and wrap
a try-except block around the base call.
:param renpy: renpy object for usage by dervied classes
"""
try:
self.__enabled = self.import_try(renpy=renpy)

except (ImportError, AttributeError, NameError) as e:
self.__enabled = False
self.log_import_error(e)
self.import_except(e, renpy=renpy)

except Exception as e:
self.__enabled = False
self.log_import_error(e)
raise e

def _set_sys_module(self, module):
"""
Sets the system module so this import can be used elsewhere.
Not called by the load system - you must call this manually
if desired.
This is also guardrailed on construction so no injection.
IN:
module - the imported module
"""
if self.set_sys:
import sys
sys.modules[self.module_name] = module
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@

from .asyncwrapper import MASAsyncWrapper
170 changes: 170 additions & 0 deletions Monika After Story/game/python-packages/mas/threading/asyncwrapper.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@

import threading

from typing import Callable

class MASAsyncWrapper(object):
"""
Class designed for calling a function asynchronously and returning
the result.
This uses threading.
NOTE: we are not using context managers because we might have an
issue with them on macs
TODO: you cannot use this to spawm multiple calls of the same function
if we need that, then we make extension of this class
Main things are:
1. storing the function to call when threading
2. not spawning a new thread if the previous one isn't done yet
(adjustable)
3. check function that checks status of thread's return value
4. closing the spawn'ed thread
PROPERTIES:
ready - True means we are ready to spawn a thread, False means we
are either waiting for the thread or waiting for main thread
to retrieve the value.
PRIVATE PROPERTIES:
_th_lock - the threading lock we are using for var checking
_th_cond - the threading condition for var checking
_th_result - data returned from the function
_th_function - the function we are calling
_th_args - args to pass into the function
_th_kwargs - kwargs to pass into the function
_th_thread - thread object
_th_done - True means the thread has returned and set the value
False means thread is still running
"""

def __init__(
self,
async_fun: Callable,
async_args: list = None,
async_kwargs: dict = None
):
"""
Constructor
:param async_fun: function to call asyncs
:param async_args: list of arguments to send to the async function
:param async_kwargs: dict of keyword args to send to the async function
"""

# setup threading stuff
self._th_lock = threading.Lock()
self._th_cond = threading.Condition(self._th_lock)
self._th_result = None
self._th_function = async_fun
self._th_args = async_args if async_args else []
self._th_kwargs = async_kwargs if async_kwargs else {}
self._th_thread = None
self._th_done = True
self.ready = True

# check for key things
if (
self._th_function is None
or self._th_args is None
or self._th_kwargs is None
):
self.ready = False


def done(self):
"""
Returns true if the thread is Done and has returned data, False
otherwise.
"""
if self.ready:
return True

is_done = False

# lock and check
self._th_cond.acquire()
is_done = self._th_done
self._th_cond.release()

return is_done


def end(self):
"""
Resets thread status vars and more so we can spawn a new thread.
Checks if the thread is done before doing any resets
"""
if self.ready or not self.done():
return

# otherwise we can reset
self.__end()


def get(self):
"""
Retrieves value set by thread and resets everything so we can
spawn a new thread.
RETURNS:
value returned from async call.
or None if the async call is still returning. (or if your
async call returned None)
"""
# dont need to waste time here if we arent running anything
if self.ready:
return self._th_result

# dont do anything if we arent done
if not self.done():
return None

# otherwise we are DONE and we return the result
ret_val = self._th_result
self.__end()

return ret_val


def start(self):
"""
Starts the threaded function call.
"""
if not self.ready:
return

# otherwise time to make new thread I guess
self._th_done = False
self._th_result = None
self.ready = False
self._th_thread = threading.Thread(target=self._th_start)
self._th_thread.daemon = True
self._th_thread.start()


def _th_start(self):
"""
Actually runs the async function and sets the result var
appropriately.
"""
temp_result = self._th_function(*self._th_args, **self._th_kwargs)

# acquire lock and set the result var
self._th_cond.acquire()
self._th_result = temp_result
self._th_done = True
self._th_cond.release()


def __end(self):
"""
Resets vars so we can spawn a new thred. Does NOT check if the
thread is done.
"""
self._th_result = None
self._th_done = True
self.ready = True

0 comments on commit 0e9fbac

Please sign in to comment.