-
Notifications
You must be signed in to change notification settings - Fork 686
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
move can import and threading into py packages, gut can import since …
…its not as needed now
- Loading branch information
1 parent
a0a326f
commit 0e9fbac
Showing
8 changed files
with
400 additions
and
624 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
|
||
|
3 changes: 3 additions & 0 deletions
3
Monika After Story/game/python-packages/mas/can_import/__init__.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
36
Monika After Story/game/python-packages/mas/can_import/exports.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
174
Monika After Story/game/python-packages/mas/can_import/masimport.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
2 changes: 2 additions & 0 deletions
2
Monika After Story/game/python-packages/mas/threading/__init__.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
|
||
from .asyncwrapper import MASAsyncWrapper |
170 changes: 170 additions & 0 deletions
170
Monika After Story/game/python-packages/mas/threading/asyncwrapper.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
Oops, something went wrong.