This commit is contained in:
shenjack 2022-11-08 20:18:01 +08:00
parent 76e4b4a1cf
commit ccaaf3b7fa
18 changed files with 327 additions and 405 deletions

View File

@ -11,7 +11,9 @@ github: @shenjackyuanjie
gitee: @shenjackyuanjie gitee: @shenjackyuanjie
""" """
from typing import Any, Dict, Callable, Union, Tuple, List from typing import Any, Dict, Callable, Union, Tuple, List, get_type_hints, Type
from Difficult_Rocket.utils.typings import Options
from libs.MCDR.version import Version from libs.MCDR.version import Version
@ -19,71 +21,6 @@ game_version = Version("0.6.3")
__version__ = game_version __version__ = game_version
class OptionNameNotDefined(Exception):
"""向初始化的 option 里添加了一个不存在于选项里的选项"""
class Options:
"""
Difficult Rocket 的游戏配置的存储基类
"""
__options: Dict[str, Union[Callable, object]] = {}
cached_options: Dict[str, Union[str, Any]] = {}
def __init__(self, **kwargs):
for option, value in kwargs.items():
if option not in self.option():
raise OptionNameNotDefined(f"option: {option} with value: {value} is not defined")
setattr(self, option, value)
self.flush_option()
def option(self) -> Dict[str, Any]:
"""
获取配置类的所有配置
:return: 自己的所有配置
"""
values = {}
for ann in self.__annotations__: # 获取类型注释
values[ann] = getattr(self, ann, None) if getattr(self, ann, None) else self.__annotations__[ann]
for option, a_fun in self.__options.items(): # 获取额外内容
values[option] = a_fun
for option, a_fun in values.items(): # 检查是否为 property
if a_fun is bool and getattr(self, option, None) is not None:
values[option] = False
if isinstance(a_fun, property):
values[option] = getattr(self, option)
return values
def flush_option(self) -> Dict[str, Any]:
"""
刷新缓存 options 的内容
:return: 刷新过的 options
"""
self.cached_options = self.option()
return self.cached_options
def option_with_len(self) -> List[Union[List[Tuple[str, Any, Any]], int, Any]]:
options = self.flush_option()
max_len_key = 1
max_len_value = 1
max_len_value_t = 1
option_list = []
for key, value in options.items():
value_t = type(value) if not isinstance(type(value), type(value)) else value
# value_t = type(value) if type(value) is not type(type(value)) else value
max_len_key = max(max_len_key, len(key))
max_len_value = max(max_len_value, len(str(value)))
max_len_value_t = max(max_len_value_t, len(str(value_t)))
option_list.append((key, value, value_t))
return [option_list, max_len_key, max_len_value, max_len_value_t]
@classmethod
def add_option(cls, name, value: Union[Callable, object]) -> Dict:
cls.__options[name] = value
return cls.__options
class _DR_option(Options): class _DR_option(Options):
""" """
@ -91,9 +28,9 @@ class _DR_option(Options):
""" """
# runtime options # runtime options
InputBox_use_TextEntry: bool = False InputBox_use_TextEntry: bool = False
use_local_logging: bool = False
record_threads: bool = True record_threads: bool = True
use_cProfile: bool = False use_cProfile: bool = False
use_local_logging: bool = False
# tests # tests
playing: bool = False playing: bool = False
@ -139,9 +76,16 @@ class _DR_runtime(Options):
DR_option = _DR_option() DR_option = _DR_option()
DR_runtime = _DR_runtime() DR_runtime = _DR_runtime()
print(DR_option.option())
print(_DR_option.option())
_DR_option.use_cProfile = True
print(_DR_option.option())
# print(_DR_option.__dict__)
# print(DR_option.__dir__())
# print(_DR_option.__dir__)
if DR_option.playing: if DR_option.playing:
from .utils import new_thread from Difficult_Rocket.utils import new_thread
def think_it(something): def think_it(something):

View File

@ -23,13 +23,13 @@ from decimal import Decimal
# Difficult_Rocket function # Difficult_Rocket function
from Difficult_Rocket import Options, DR_runtime from Difficult_Rocket import Options, DR_runtime
from Difficult_Rocket.command import line, tree from Difficult_Rocket.command import line, tree
from Difficult_Rocket.utils import new_thread
from Difficult_Rocket.utils.translate import tr from Difficult_Rocket.utils.translate import tr
from Difficult_Rocket.client.guis.widgets import InputBox
# from Difficult_Rocket.client.screen import DRScreen # from Difficult_Rocket.client.screen import DRScreen
# from Difficult_Rocket.client.screen import DRDEBUGScreen # from Difficult_Rocket.client.screen import DRDEBUGScreen
from Difficult_Rocket.utils import tools, translate from Difficult_Rocket.utils import tools, translate
from Difficult_Rocket.utils.new_thread import new_thread
from Difficult_Rocket.client.fps.fps_log import FpsLogger from Difficult_Rocket.client.fps.fps_log import FpsLogger
from Difficult_Rocket.client.guis.widgets import InputBox
from Difficult_Rocket.exception.command import CommandError from Difficult_Rocket.exception.command import CommandError
import tomlkit import tomlkit

View File

@ -18,8 +18,9 @@ from typing import Union
from decimal import Decimal from decimal import Decimal
# from DR # from DR
from Difficult_Rocket.utils import translate
from Difficult_Rocket.command.api import CommandText from Difficult_Rocket.command.api import CommandText
from Difficult_Rocket.utils import new_thread, translate from Difficult_Rocket.utils.new_thread import new_thread
# from pyglet # from pyglet
import pyglet import pyglet

View File

@ -11,6 +11,11 @@ github: @shenjackyuanjie
gitee: @shenjackyuanjie gitee: @shenjackyuanjie
""" """
from .new_thread import new_thread from typing import TYPE_CHECKING
if TYPE_CHECKING:
from .new_thread import new_thread
from .typings import get_type_hints_
__all__ = ['new_thread', 'get_type_hints_']
__all__ = ['new_thread']

View File

@ -21,7 +21,7 @@ import configparser
from typing import Union from typing import Union
from xml.dom.minidom import parse from xml.dom.minidom import parse
from libs import toml import tomlkit
from Difficult_Rocket.exception.unsupport import NoMoreJson5 from Difficult_Rocket.exception.unsupport import NoMoreJson5
@ -50,7 +50,8 @@ def load_file(file_name: str, stack: Union[str, list, dict] = None, raise_error:
if stack: if stack:
get_file = get_file[stack] get_file = get_file[stack]
elif f_type == 'toml': elif f_type == 'toml':
get_file = toml.load(file_name) with open(file_name, mode='r', encoding='utf-8') as file:
get_file = tomlkit.load(file)
elif f_type == 'json5': elif f_type == 'json5':
raise NoMoreJson5("我说什么也不用json5了喵的") raise NoMoreJson5("我说什么也不用json5了喵的")
except Exception as exp: except Exception as exp:

View File

@ -0,0 +1,84 @@
# -------------------------------
# Difficult Rocket
# Copyright © 2021-2022 by shenjackyuanjie 3695888@qq.com
# All rights reserved
# -------------------------------
from typing import get_type_hints, Type, List, Union, Dict, Any, Callable, Tuple
def get_type_hints_(cls: Type):
try:
return get_type_hints(cls)
except ValueError:
return get_type_hints(cls, globalns={})
class OptionNameNotDefined(Exception):
"""向初始化的 option 里添加了一个不存在于选项里的选项"""
class Options:
"""
Difficult Rocket 的游戏配置的存储基类
"""
__options: Dict[str, Union[Callable, object]] = {}
cached_options: Dict[str, Union[str, Any]] = {}
def __init__(self, **kwargs):
for option, value in kwargs.items():
if option not in self.option():
raise OptionNameNotDefined(f"option: {option} with value: {value} is not defined")
setattr(self, option, value)
self.flush_option()
@classmethod
def option(cls) -> Dict[str, Any]:
"""
获取配置类的所有配置
:return: 自己的所有配置
"""
values = {}
for ann in cls.__annotations__: # 获取类型注释
values[ann] = getattr(cls, ann, None)
if values[ann] is None:
values[ann] = cls.__annotations__[ann]
for option, a_fun in cls.__options.items(): # 获取额外内容
values[option] = a_fun
for option, a_fun in values.items(): # 检查是否为 property
if a_fun is bool and getattr(cls, option, None) is not None:
values[option] = False
if isinstance(a_fun, property):
values[option] = getattr(cls, option)
return values
@classmethod
def flush_option(cls) -> Dict[str, Any]:
"""
刷新缓存 options 的内容
:return: 刷新过的 options
"""
cls.cached_options = cls.option()
return cls.cached_options
@classmethod
def option_with_len(cls) -> List[Union[List[Tuple[str, Any, Any]], int, Any]]:
options = cls.flush_option()
max_len_key = 1
max_len_value = 1
max_len_value_t = 1
option_list = []
for key, value in options.items():
value_t = type(value) if not isinstance(value, Type) else value
max_len_key = max(max_len_key, len(key))
max_len_value = max(max_len_value, len(str(value)))
max_len_value_t = max(max_len_value_t, len(str(value_t)))
option_list.append((key, value, value_t))
return [option_list, max_len_key, max_len_value, max_len_value_t]
@classmethod
def add_option(cls, name, value: Union[Callable, object]) -> Dict:
cls.__options[name] = value
return cls.__options

6
compile.ps1 Normal file
View File

@ -0,0 +1,6 @@
allPython.ps1 -m compileall 'libs\'
allPython.ps1 -m compileall 'Difficult_Rocket\'
allPython.ps1 -O -m compileall 'libs\'
allPython.ps1 -O -m compileall 'Difficult_Rocket\'
allPython.ps1 -OO -m compileall 'libs\'
allPython.ps1 -OO -m compileall 'Difficult_Rocket\'

View File

@ -1,4 +0,0 @@
python3.8 -O -m compileall .\libs\
python3.8 -O -m compileall .\Difficult_Rocket\
python3.10 -O -m compileall .\libs\
python3.10 -O -m compileall .\Difficult_Rocket\

View File

@ -40,7 +40,7 @@ def _get_origin(cls: Type):
def _get_args(cls: Type) -> tuple: def _get_args(cls: Type) -> tuple:
return getattr(cls, '__args__', ()) return getattr(cls, '__args__', ())
_BASIC_CLASSES = (type(None), bool, int, float, str, list, dict, Version) _BASIC_CLASSES = (type(None), bool, int, float, str, list, dict, Version)

View File

@ -163,7 +163,10 @@ class EventLoop(event.EventDispatcher):
Developers are discouraged from overriding this method, as the Developers are discouraged from overriding this method, as the
implementation is platform-specific. implementation is platform-specific.
""" """
self.clock.schedule_interval(self._redraw_windows, interval) if not interval:
self.clock.schedule(self._redraw_windows)
else:
self.clock.schedule_interval(self._redraw_windows, interval)
self.has_exit = False self.has_exit = False

View File

@ -35,26 +35,13 @@
"""Precise framerate calculation function scheduling. """Precise framerate calculation function scheduling.
Measuring time The :py:mod:`~pyglet.clock` module allows you to schedule functions
============== to run periodically, or for one-shot future execution. pyglet's default
event loop (:py:func:`~pyglet.app.run`) keeps an internal instance of
a :py:class:`~pyglet.clock.Clock`, which is ticked automatically.
The `tick` and `get_frequency` functions can be used in conjunction to fulfil most ..note:: Some internal modules will schedule items on the clock. If you
games' basic requirements:: are using a custom event loop, always remember to `tick` the clock!
from pyglet import clock
while True:
dt = clock.tick()
# ... update and render ...
print(f"FPS is {clock.get_frequency()}")
The ``dt`` value returned gives the number of seconds (as a float) since the
last "tick".
The `get_frequency` function averages the framerate over a sliding window of
approximately 1 second. (You can calculate the instantaneous framerate by
taking the reciprocal of ``dt``).
Always remember to `tick` the clock!
Scheduling Scheduling
========== ==========
@ -69,18 +56,18 @@ You can schedule a function to be called every time the clock is ticked::
The `schedule_interval` method causes a function to be called every "n" The `schedule_interval` method causes a function to be called every "n"
seconds:: seconds::
clock.schedule_interval(callback, .5) # called twice a second clock.schedule_interval(callback, 0.5) # called twice a second
The `schedule_once` method causes a function to be called once "n" seconds The `schedule_once` method causes a function to be called once "n" seconds
in the future:: in the future::
clock.schedule_once(callback, 5) # called in 5 seconds clock.schedule_once(callback, 5) # called in 5 seconds
All of the `schedule` methods will pass on any additional args or keyword args All the `schedule` methods will pass on any additional args or keyword args
you specify to the callback function:: you specify to the callback function::
def move(dt, velocity, sprite): def move(dt, velocity, sprite):
sprite.position += dt * velocity sprite.position += dt * velocity
clock.schedule(move, velocity=5.0, sprite=alien) clock.schedule(move, velocity=5.0, sprite=alien)
@ -96,14 +83,14 @@ The clock functions are all relayed to an instance of
:py:class:`~pyglet.clock.Clock` which is initialised with the module. You can :py:class:`~pyglet.clock.Clock` which is initialised with the module. You can
get this instance to use directly:: get this instance to use directly::
clk = clock.get_default() clk = pyglet.clock.get_default()
You can also replace the default clock with your own: You can also replace the default clock with your own:
myclk = clock.Clock() myclk = pyglet.clock.Clock()
clock.set_default(myclk) pyglet.clock.set_default(myclk)
Each clock maintains its own set of scheduled functions and FPS Each clock maintains its own set of scheduled functions and frequency
measurement. Each clock must be "ticked" separately. measurement. Each clock must be "ticked" separately.
Multiple and derived clocks potentially allow you to separate "game-time" and Multiple and derived clocks potentially allow you to separate "game-time" and
@ -113,6 +100,7 @@ of the system clock.
import time as _time import time as _time
from typing import Callable
from heapq import heappop as _heappop from heapq import heappop as _heappop
from heapq import heappush as _heappush from heapq import heappush as _heappush
from heapq import heappushpop as _heappushpop from heapq import heappushpop as _heappushpop
@ -164,14 +152,11 @@ class Clock:
def __init__(self, time_function=_time.perf_counter): def __init__(self, time_function=_time.perf_counter):
"""Initialise a Clock, with optional custom time function. """Initialise a Clock, with optional custom time function.
:Parameters: You can provide a custom time function to return the elapsed
`time_function` : function time of the application, in seconds. Defaults to time.perf_counter,
Function to return the elapsed time of the application, but can be replaced to allow for easy time dilation effects or game
in seconds. Defaults to time.time, but can be replaced pausing.
to allow for easy time dilation effects or game pausing.
""" """
super(Clock, self).__init__()
self.time = time_function self.time = time_function
self.next_ts = self.time() self.next_ts = self.time()
self.last_ts = None self.last_ts = None
@ -263,7 +248,7 @@ class Clock:
else: else:
item = _heappushpop(interval_items, item) item = _heappushpop(interval_items, item)
# a scheduled function may try and unschedule itself # a scheduled function may try to unschedule itself,
# so we need to keep a reference to the current # so we need to keep a reference to the current
# item no longer on heap to be able to check # item no longer on heap to be able to check
self._current_interval_item = item self._current_interval_item = item
@ -288,15 +273,15 @@ class Clock:
# test the schedule for the next execution # test the schedule for the next execution
if item.next_ts <= now: if item.next_ts <= now:
# the scheduled time of this item has already passed # the scheduled time of this item has already
# so it must be rescheduled # passed, so it must be rescheduled
if now - item.next_ts < 0.05: if now - item.next_ts < 0.05:
# missed execution time by 'reasonable' amount, so # missed execution time by 'reasonable' amount, so
# reschedule at normal interval # reschedule at normal interval
item.next_ts = now + item.interval item.next_ts = now + item.interval
else: else:
# missed by significant amount, now many events have # missed by significant amount, now many events have
# likely missed execution. do a soft reschedule to # likely missed execution. do a soft re-schedule to
# avoid lumping many events together. # avoid lumping many events together.
# in this case, the next dt will not be accurate # in this case, the next dt will not be accurate
item.next_ts = get_soft_next_ts(now, item.interval) item.next_ts = get_soft_next_ts(now, item.interval)
@ -400,6 +385,7 @@ class Clock:
return last_ts return last_ts
def _get_soft_next_ts(self, last_ts, interval): def _get_soft_next_ts(self, last_ts, interval):
def taken(ts, e): def taken(ts, e):
"""Check if `ts` has already got an item scheduled nearby.""" """Check if `ts` has already got an item scheduled nearby."""
# TODO this function is slow and called very often. # TODO this function is slow and called very often.
@ -412,9 +398,9 @@ class Clock:
return False return False
# sorted list is required required to produce expected results # sorted list is required to produce expected results
# taken() will iterate through the heap, expecting it to be sorted # taken() will iterate through the heap, expecting it to be sorted
# and will not always catch smallest value, so sort here. # and will not always catch the smallest value, so sort here.
# do not remove the sort key...it is faster than relaying comparisons # do not remove the sort key...it is faster than relaying comparisons
# NOTE: do not rewrite as popping from heap, as that is super slow! # NOTE: do not rewrite as popping from heap, as that is super slow!
self._schedule_interval_items.sort(key=_attrgetter('next_ts')) self._schedule_interval_items.sort(key=_attrgetter('next_ts'))
@ -512,7 +498,7 @@ class Clock:
This method is similar to `schedule_interval`, except that the This method is similar to `schedule_interval`, except that the
clock will move the interval out of phase with other scheduled clock will move the interval out of phase with other scheduled
functions so as to distribute CPU more load evenly over time. functions in order to distribute CPU load more evenly.
This is useful for functions that need to be called regularly, This is useful for functions that need to be called regularly,
but not relative to the initial start time. :py:mod:`pyglet.media` but not relative to the initial start time. :py:mod:`pyglet.media`
@ -572,14 +558,10 @@ class Clock:
_default = Clock() _default = Clock()
def set_default(default): def set_default(default) -> None:
"""Set the default clock to use for all module-level functions. """Set the default clock to use for all module-level functions.
By default an instance of :py:class:`~pyglet.clock.Clock` is used. By default, an instance of :py:class:`~pyglet.clock.Clock` is used.
:Parameters:
`default` : `Clock`
The default clock to use.
""" """
global _default global _default
_default = default _default = default
@ -590,17 +572,16 @@ def get_default():
Return the :py:class:`~pyglet.clock.Clock` instance that is used by all Return the :py:class:`~pyglet.clock.Clock` instance that is used by all
module-level clock functions. module-level clock functions.
:rtype: `Clock`
:return: The default clock.
""" """
return _default return _default
def tick(poll=False): def tick(poll: bool = False) -> float:
"""Signify that one frame has passed on the default clock. """Signify that one frame has passed on the default clock.
This will call any scheduled functions that have elapsed. This will call any scheduled functions that have elapsed,
and return the elapsed seconds since the last tick. The
return value will be 0.0 if this is the first tick.
:Parameters: :Parameters:
`poll` : bool `poll` : bool
@ -610,17 +591,16 @@ def tick(poll=False):
only. only.
Since pyglet 1.1. Since pyglet 1.1.
:rtype: float
:return: The number of seconds since the last "tick", or 0 if this was the
first frame.
""" """
return _default.tick(poll) return _default.tick(poll)
def get_sleep_time(sleep_idle): def get_sleep_time(sleep_idle: bool) -> float:
"""Get the time until the next item is scheduled on the default clock. """Get the time until the next item is scheduled on the default clock.
Returns the time until the next scheduled event in seconds, or
``None`` if there is no event scheduled.
See `Clock.get_sleep_time` for details. See `Clock.get_sleep_time` for details.
:Parameters: :Parameters:
@ -628,17 +608,11 @@ def get_sleep_time(sleep_idle):
If True, the application intends to sleep through its idle If True, the application intends to sleep through its idle
time; otherwise it will continue ticking at the maximum time; otherwise it will continue ticking at the maximum
frame rate allowed. frame rate allowed.
:rtype: float
:return: Time until the next scheduled event in seconds, or ``None``
if there is no event scheduled.
.. versionadded:: 1.1
""" """
return _default.get_sleep_time(sleep_idle) return _default.get_sleep_time(sleep_idle)
def get_frequency(): def get_frequency() -> float:
"""Get the average clock update frequency. """Get the average clock update frequency.
The result is the sliding average of the last "n" updates, The result is the sliding average of the last "n" updates,
@ -646,65 +620,43 @@ def get_frequency():
second. This is the internal clock update rate, **not** the second. This is the internal clock update rate, **not** the
Window redraw rate. Platform events, such as moving the Window redraw rate. Platform events, such as moving the
mouse rapidly, will cause the clock to refresh more often. mouse rapidly, will cause the clock to refresh more often.
:rtype: float
:return: The measured updates per second.
""" """
return _default.get_frequency() return _default.get_frequency()
def schedule(func, *args, **kwargs): def schedule(func: Callable, *args, **kwargs) -> None:
"""Schedule 'func' to be called every frame on the default clock. """Schedule 'func' to be called every frame on the default clock.
The arguments passed to func are ``dt``, followed by any ``*args`` and The arguments passed to func are ``dt``, followed by any ``*args`` and
``**kwargs`` given here. ``**kwargs`` given here.
:Parameters:
`func` : callable
The function to call each frame.
""" """
_default.schedule(func, *args, **kwargs) _default.schedule(func, *args, **kwargs)
def schedule_interval(func, interval, *args, **kwargs): def schedule_interval(func: Callable, interval: float, *args, **kwargs) -> None:
"""Schedule ``func`` on the default clock every interval seconds. """Schedule ``func`` on the default clock every ``interval`` seconds.
The arguments passed to ``func`` are ``dt`` (time since last function The arguments passed to ``func`` are ``dt`` (time since last function
call), followed by any ``*args`` and ``**kwargs`` given here. call), followed by any ``*args`` and ``**kwargs`` given here.
:Parameters:
`func` : callable
The function to call when the timer lapses.
`interval` : float
The number of seconds to wait between each call.
""" """
_default.schedule_interval(func, interval, *args, **kwargs) _default.schedule_interval(func, interval, *args, **kwargs)
def schedule_interval_soft(func, interval, *args, **kwargs): def schedule_interval_soft(func: Callable, interval: float, *args, **kwargs) -> None:
"""Schedule ``func`` on the default clock every interval seconds. """Schedule ``func`` on the default clock every interval seconds.
The clock will move the interval out of phase with other scheduled The clock will move the interval out of phase with other scheduled
functions so as to distribute CPU more load evenly over time. functions in order to distribute CPU load more evenly.
The arguments passed to ``func`` are ``dt`` (time since last function The arguments passed to ``func`` are ``dt`` (time since last function
call), followed by any ``*args`` and ``**kwargs`` given here. call), followed by any ``*args`` and ``**kwargs`` given here.
:see: `Clock.schedule_interval_soft` :see: `Clock.schedule_interval_soft`
.. versionadded:: 1.1
:Parameters:
`func` : callable
The function to call when the timer lapses.
`interval` : float
The number of seconds to wait between each call.
""" """
_default.schedule_interval_soft(func, interval, *args, **kwargs) _default.schedule_interval_soft(func, interval, *args, **kwargs)
def schedule_once(func, delay, *args, **kwargs): def schedule_once(func: Callable, delay: float, *args, **kwargs) -> None:
"""Schedule ``func`` to be called once after ``delay`` seconds. """Schedule ``func`` to be called once after ``delay`` seconds.
This function uses the default clock. ``delay`` can be a float. The This function uses the default clock. ``delay`` can be a float. The
@ -713,23 +665,13 @@ def schedule_once(func, delay, *args, **kwargs):
If no default clock is set, the func is queued and will be scheduled If no default clock is set, the func is queued and will be scheduled
on the default clock as soon as it is created. on the default clock as soon as it is created.
:Parameters:
`func` : callable
The function to call when the timer lapses.
`delay` : float
The number of seconds to wait before the timer lapses.
""" """
_default.schedule_once(func, delay, *args, **kwargs) _default.schedule_once(func, delay, *args, **kwargs)
def unschedule(func): def unschedule(func: Callable) -> None:
"""Remove ``func`` from the default clock's schedule. """Remove ``func`` from the default clock's schedule.
No error is raised if the ``func`` was never scheduled. No error is raised if the ``func`` was never scheduled.
:Parameters:
`func` : callable
The function to remove from the schedule.
""" """
_default.unschedule(func) _default.unschedule(func)

View File

@ -1550,7 +1550,7 @@ if not wic_decoder:
class DirectWriteGlyphRenderer(base.GlyphRenderer): class DirectWriteGlyphRenderer(base.GlyphRenderer):
antialias_mode = D2D1_TEXT_ANTIALIAS_MODE_DEFAULT antialias_mode = D2D1_TEXT_ANTIALIAS_MODE_DEFAULT
draw_options = D2D1_DRAW_TEXT_OPTIONS_ENABLE_COLOR_FONT draw_options = D2D1_DRAW_TEXT_OPTIONS_ENABLE_COLOR_FONT if WINDOWS_8_1_OR_GREATER else D2D1_DRAW_TEXT_OPTIONS_NONE
measuring_mode = DWRITE_MEASURING_MODE_NATURAL measuring_mode = DWRITE_MEASURING_MODE_NATURAL
def __init__(self, font): def __init__(self, font):

View File

@ -91,46 +91,13 @@ possible. If the drawing of sprites or text objects need to be interleaved
with other drawing that does not use the graphics API, multiple batches will with other drawing that does not use the graphics API, multiple batches will
be required. be required.
Data item parameters
====================
Many of the functions and methods in this module accept any number of ``data``
parameters as their final parameters. In the documentation these are notated
as ``*data`` in the formal parameter list.
A data parameter describes a vertex attribute format and an optional sequence
to initialise that attribute. Examples of common attribute formats are:
``"v3f"``
Vertex position, specified as three floats.
``"c4B"``
Vertex color, specified as four unsigned bytes.
``"t2f"``
Texture coordinate, specified as two floats.
See `pyglet.graphics.vertexattribute` for the complete syntax of the vertex
format string.
When no initial data is to be given, the data item is just the format string.
For example, the following creates a 2 element vertex list with position and
color attributes::
vertex_list = pyglet.graphics.vertex_list(2, 'v2f', 'c4B')
When initial data is required, wrap the format string and the initial data in
a tuple, for example::
vertex_list = pyglet.graphics.vertex_list(2,
('v2f', (0.0, 1.0, 1.0, 0.0)),
('c4B', (255, 255, 255, 255) * 2))
Drawing modes Drawing modes
============= =============
Methods in this module that accept a ``mode`` parameter will accept any value Methods in this module that accept a ``mode`` parameter will accept any value
in the OpenGL drawing mode enumeration: ``GL_POINTS``, ``GL_LINE_STRIP``, in the OpenGL drawing mode enumeration: ``GL_POINTS``, ``GL_LINE_STRIP``,
``GL_LINE_LOOP``, ``GL_LINES``, ``GL_TRIANGLE_STRIP``, ``GL_TRIANGLE_FAN``, ``GL_LINE_LOOP``, ``GL_LINES``, ``GL_TRIANGLE_STRIP``, ``GL_TRIANGLE_FAN``,
``GL_TRIANGLES``, ``GL_QUAD_STRIP``, ``GL_QUADS``, and ``GL_POLYGON``. ``GL_TRIANGLES``, and ``GL_POLYGON``.
:: ::
@ -140,8 +107,8 @@ However, because of the way the graphics API renders multiple primitives with
shared state, ``GL_POLYGON``, ``GL_LINE_LOOP`` and ``GL_TRIANGLE_FAN`` cannot shared state, ``GL_POLYGON``, ``GL_LINE_LOOP`` and ``GL_TRIANGLE_FAN`` cannot
be used --- the results are undefined. be used --- the results are undefined.
When using ``GL_LINE_STRIP``, ``GL_TRIANGLE_STRIP`` or ``GL_QUAD_STRIP`` care When using ``GL_LINE_STRIP`` or ``GL_TRIANGLE_STRIP``, care must be taken to
must be taken to insert degenerate vertices at the beginning and end of each insert degenerate vertices at the beginning and end of each
vertex list. For example, given the vertex list:: vertex list. For example, given the vertex list::
A, B, C, D A, B, C, D
@ -163,7 +130,7 @@ import weakref
import pyglet import pyglet
from pyglet.gl import * from pyglet.gl import *
from pyglet.graphics import vertexattribute, vertexdomain from pyglet.graphics import shader, vertexdomain
from pyglet.graphics.vertexarray import VertexArray from pyglet.graphics.vertexarray import VertexArray
from pyglet.graphics.vertexbuffer import BufferObject from pyglet.graphics.vertexbuffer import BufferObject
@ -199,7 +166,7 @@ def draw(size, mode, **data):
count = program.attributes[name]['count'] count = program.attributes[name]['count']
gl_type = vertexdomain._gl_types[fmt[0]] gl_type = vertexdomain._gl_types[fmt[0]]
normalize = 'n' in fmt normalize = 'n' in fmt
attribute = vertexattribute.VertexAttribute(name, location, count, gl_type, normalize) attribute = shader.Attribute(name, location, count, gl_type, normalize)
assert size == len(array) // attribute.count, 'Data for %s is incorrect length' % fmt assert size == len(array) // attribute.count, 'Data for %s is incorrect length' % fmt
buffer = BufferObject(size * attribute.stride) buffer = BufferObject(size * attribute.stride)
@ -249,7 +216,7 @@ def draw_indexed(size, mode, indices, **data):
count = program.attributes[name]['count'] count = program.attributes[name]['count']
gl_type = vertexdomain._gl_types[fmt[0]] gl_type = vertexdomain._gl_types[fmt[0]]
normalize = 'n' in fmt normalize = 'n' in fmt
attribute = vertexattribute.VertexAttribute(name, location, count, gl_type, normalize) attribute = shader.Attribute(name, location, count, gl_type, normalize)
assert size == len(array) // attribute.count, 'Data for %s is incorrect length' % fmt assert size == len(array) // attribute.count, 'Data for %s is incorrect length' % fmt
buffer = BufferObject(size * attribute.stride) buffer = BufferObject(size * attribute.stride)

View File

@ -14,6 +14,17 @@ class ShaderException(BaseException):
pass pass
_c_types = {
GL_BYTE: c_byte,
GL_UNSIGNED_BYTE: c_ubyte,
GL_SHORT: c_short,
GL_UNSIGNED_SHORT: c_ushort,
GL_INT: c_int,
GL_UNSIGNED_INT: c_uint,
GL_FLOAT: c_float,
GL_DOUBLE: c_double,
}
# TODO: test other shader types, and update if necessary. # TODO: test other shader types, and update if necessary.
_shader_types = { _shader_types = {
'vertex': GL_VERTEX_SHADER, 'vertex': GL_VERTEX_SHADER,
@ -91,6 +102,102 @@ _attribute_types = {
} }
class Attribute:
"""Abstract accessor for an attribute in a mapped buffer."""
def __init__(self, name, location, count, gl_type, normalize):
"""Create the attribute accessor.
:Parameters:
`name` : str
Name of the vertex attribute.
`location` : int
Location (index) of the vertex attribute.
`count` : int
Number of components in the attribute.
`gl_type` : int
OpenGL type enumerant; for example, ``GL_FLOAT``
`normalize`: bool
True if OpenGL should normalize the values
"""
self.name = name
self.location = location
self.count = count
self.gl_type = gl_type
self.c_type = _c_types[gl_type]
self.normalize = normalize
self.align = sizeof(self.c_type)
self.size = count * self.align
self.stride = self.size
def enable(self):
"""Enable the attribute."""
glEnableVertexAttribArray(self.location)
def set_pointer(self, ptr):
"""Setup this attribute to point to the currently bound buffer at
the given offset.
``offset`` should be based on the currently bound buffer's ``ptr``
member.
:Parameters:
`offset` : int
Pointer offset to the currently bound buffer for this
attribute.
"""
glVertexAttribPointer(self.location, self.count, self.gl_type, self.normalize, self.stride, ptr)
def get_region(self, buffer, start, count):
"""Map a buffer region using this attribute as an accessor.
The returned region consists of a contiguous array of component
data elements. For example, if this attribute uses 3 floats per
vertex, and the `count` parameter is 4, the number of floats mapped
will be ``3 * 4 = 12``.
:Parameters:
`buffer` : `AbstractMappable`
The buffer to map.
`start` : int
Offset of the first vertex to map.
`count` : int
Number of vertices to map
:rtype: `AbstractBufferRegion`
"""
byte_start = self.stride * start
byte_size = self.stride * count
array_count = self.count * count
ptr_type = POINTER(self.c_type * array_count)
return buffer.get_region(byte_start, byte_size, ptr_type)
def set_region(self, buffer, start, count, data):
"""Set the data over a region of the buffer.
:Parameters:
`buffer` : AbstractMappable`
The buffer to modify.
`start` : int
Offset of the first vertex to set.
`count` : int
Number of vertices to set.
`data` : A sequence of data components.
"""
byte_start = self.stride * start
byte_size = self.stride * count
array_count = self.count * count
data = (self.c_type * array_count)(*data)
buffer.set_data_region(data, byte_start, byte_size)
def __repr__(self):
return f"Attribute(name='{self.name}', location={self.location}, count={self.count})"
class _Uniform: class _Uniform:
__slots__ = 'program', 'name', 'type', 'location', 'length', 'count', 'get', 'set' __slots__ = 'program', 'name', 'type', 'location', 'length', 'count', 'get', 'set'

View File

@ -1,148 +0,0 @@
# ----------------------------------------------------------------------------
# pyglet
# Copyright (c) 2006-2008 Alex Holkner
# Copyright (c) 2008-2022 pyglet contributors
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in
# the documentation and/or other materials provided with the
# distribution.
# * Neither the name of pyglet nor the names of its
# contributors may be used to endorse or promote products
# derived from this software without specific prior written
# permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
# ----------------------------------------------------------------------------
import ctypes
from pyglet.gl import *
_c_types = {
GL_BYTE: ctypes.c_byte,
GL_UNSIGNED_BYTE: ctypes.c_ubyte,
GL_SHORT: ctypes.c_short,
GL_UNSIGNED_SHORT: ctypes.c_ushort,
GL_INT: ctypes.c_int,
GL_UNSIGNED_INT: ctypes.c_uint,
GL_FLOAT: ctypes.c_float,
GL_DOUBLE: ctypes.c_double,
}
class VertexAttribute:
"""Abstract accessor for an attribute in a mapped buffer."""
def __init__(self, name, location, count, gl_type, normalize):
"""Create the attribute accessor.
:Parameters:
`name` : str
Name of the vertex attribute.
`location` : int
Location (index) of the vertex attribute.
`count` : int
Number of components in the attribute.
`gl_type` : int
OpenGL type enumerant; for example, ``GL_FLOAT``
`normalize`: bool
True if OpenGL should normalize the values
"""
self.name = name
self.location = location
self.count = count
self.gl_type = gl_type
self.c_type = _c_types[gl_type]
self.normalize = normalize
self.align = ctypes.sizeof(self.c_type)
self.size = count * self.align
self.stride = self.size
def enable(self):
"""Enable the attribute."""
glEnableVertexAttribArray(self.location)
def set_pointer(self, pointer):
"""Setup this attribute to point to the currently bound buffer at
the given offset.
``offset`` should be based on the currently bound buffer's ``ptr``
member.
:Parameters:
`offset` : int
Pointer offset to the currently bound buffer for this
attribute.
"""
glVertexAttribPointer(self.location, self.count, self.gl_type, self.normalize, self.stride, pointer)
def get_region(self, buffer, start, count):
"""Map a buffer region using this attribute as an accessor.
The returned region consists of a contiguous array of component
data elements. For example, if this attribute uses 3 floats per
vertex, and the `count` parameter is 4, the number of floats mapped
will be ``3 * 4 = 12``.
:Parameters:
`buffer` : `AbstractMappable`
The buffer to map.
`start` : int
Offset of the first vertex to map.
`count` : int
Number of vertices to map
:rtype: `AbstractBufferRegion`
"""
byte_start = self.stride * start
byte_size = self.stride * count
array_count = self.count * count
ptr_type = ctypes.POINTER(self.c_type * array_count)
return buffer.get_region(byte_start, byte_size, ptr_type)
def set_region(self, buffer, start, count, data):
"""Set the data over a region of the buffer.
:Parameters:
`buffer` : AbstractMappable`
The buffer to modify.
`start` : int
Offset of the first vertex to set.
`count` : int
Number of vertices to set.
`data` : sequence
Sequence of data components.
"""
byte_start = self.stride * start
byte_size = self.stride * count
array_count = self.count * count
data = (self.c_type * array_count)(*data)
buffer.set_data_region(data, byte_start, byte_size)
def __repr__(self):
return f"VertexAttribute(name='{self.name}', location={self.location}, count={self.count})"

View File

@ -61,7 +61,7 @@ import ctypes
import pyglet import pyglet
from pyglet.gl import * from pyglet.gl import *
from pyglet.graphics import allocation, vertexattribute, vertexarray from pyglet.graphics import allocation, shader, vertexarray
from pyglet.graphics.vertexbuffer import BufferObject, MappableBufferObject from pyglet.graphics.vertexbuffer import BufferObject, MappableBufferObject
@ -125,7 +125,7 @@ class VertexDomain:
count = meta['count'] count = meta['count']
gl_type = _gl_types[meta['format'][0]] gl_type = _gl_types[meta['format'][0]]
normalize = 'n' in meta['format'] normalize = 'n' in meta['format']
attribute = vertexattribute.VertexAttribute(name, location, count, gl_type, normalize) attribute = shader.Attribute(name, location, count, gl_type, normalize)
self.attributes.append(attribute) self.attributes.append(attribute)
# Create buffer: # Create buffer:
@ -370,7 +370,7 @@ class IndexedVertexDomain(VertexDomain):
self.index_allocator = allocation.Allocator(self._initial_index_count) self.index_allocator = allocation.Allocator(self._initial_index_count)
self.index_gl_type = index_gl_type self.index_gl_type = index_gl_type
self.index_c_type = vertexattribute._c_types[index_gl_type] self.index_c_type = shader._c_types[index_gl_type]
self.index_element_size = ctypes.sizeof(self.index_c_type) self.index_element_size = ctypes.sizeof(self.index_c_type)
self.index_buffer = BufferObject(self.index_allocator.capacity * self.index_element_size) self.index_buffer = BufferObject(self.index_allocator.capacity * self.index_element_size)

View File

@ -818,6 +818,31 @@ class Mat4(tuple):
0, 0, q, -1, 0, 0, q, -1,
0, 0, qn, 0)) 0, 0, qn, 0))
@classmethod
def from_rotation(cls, angle: float, vector: Vec3) -> Mat4:
"""Create a rotation matrix from an angle and Vec3.
:Parameters:
`angle` : A `float` :
The angle as a float.
`vector` : A `Vec3`, or 3 component tuple of float or int :
Vec3 or tuple with x, y and z translation values
"""
return cls().rotate(angle, vector)
@classmethod
def from_scale(cls: type[Mat4T], vector: Vec3) -> Mat4T:
"""Create a scale matrix from a Vec3.
:Parameters:
`vector` : A `Vec3`, or 3 component tuple of float or int
Vec3 or tuple with x, y and z scale values
"""
return cls((vector[0], 0.0, 0.0, 0.0,
0.0, vector[1], 0.0, 0.0,
0.0, 0.0, vector[2], 0.0,
0.0, 0.0, 0.0, 1.0))
@classmethod @classmethod
def from_translation(cls: type[Mat4T], vector: Vec3) -> Mat4T: def from_translation(cls: type[Mat4T], vector: Vec3) -> Mat4T:
"""Create a translation matrix from a Vec3. """Create a translation matrix from a Vec3.
@ -831,18 +856,6 @@ class Mat4(tuple):
0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0, 0.0,
vector[0], vector[1], vector[2], 1.0)) vector[0], vector[1], vector[2], 1.0))
@classmethod
def from_rotation(cls, angle: float, vector: Vec3) -> Mat4:
"""Create a rotation matrix from an angle and Vec3.
:Parameters:
`angle` : A `float` :
The angle as a float.
`vector` : A `Vec3`, or 3 component tuple of float or int :
Vec3 or tuple with x, y and z translation values
"""
return cls().rotate(angle, vector)
@classmethod @classmethod
def look_at_direction(cls: type[Mat4T], direction: Vec3, up: Vec3) -> Mat4T: def look_at_direction(cls: type[Mat4T], direction: Vec3, up: Vec3) -> Mat4T:
vec_z = direction.normalize() vec_z = direction.normalize()
@ -869,18 +882,6 @@ class Mat4(tuple):
"""Get a specific column as a tuple.""" """Get a specific column as a tuple."""
return self[index::4] return self[index::4]
def scale(self, vector: Vec3) -> Mat4:
"""Get a scale Matrix on x, y, or z axis."""
temp = list(self)
temp[0] *= vector[0]
temp[5] *= vector[1]
temp[10] *= vector[2]
return Mat4(temp)
def translate(self, vector: Vec3) -> Mat4:
"""Get a translation Matrix along x, y, and z axis."""
return self @ Mat4((1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, *vector, 1))
def rotate(self, angle: float, vector: Vec3) -> Mat4: def rotate(self, angle: float, vector: Vec3) -> Mat4:
"""Get a rotation Matrix on x, y, or z axis.""" """Get a rotation Matrix on x, y, or z axis."""
if not all(abs(n) <= 1 for n in vector): if not all(abs(n) <= 1 for n in vector):
@ -908,6 +909,18 @@ class Mat4(tuple):
return Mat4(self) @ Mat4((ra, rb, rc, 0, re, rf, rg, 0, ri, rj, rk, 0, 0, 0, 0, 1)) return Mat4(self) @ Mat4((ra, rb, rc, 0, re, rf, rg, 0, ri, rj, rk, 0, 0, 0, 0, 1))
def scale(self, vector: Vec3) -> Mat4:
"""Get a scale Matrix on x, y, or z axis."""
temp = list(self)
temp[0] *= vector[0]
temp[5] *= vector[1]
temp[10] *= vector[2]
return Mat4(temp)
def translate(self, vector: Vec3) -> Mat4:
"""Get a translation Matrix along x, y, and z axis."""
return self @ Mat4((1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, *vector, 1))
def transpose(self) -> Mat4: def transpose(self) -> Mat4:
"""Get a transpose of this Matrix.""" """Get a transpose of this Matrix."""
return Mat4(self[0::4] + self[1::4] + self[2::4] + self[3::4]) return Mat4(self[0::4] + self[1::4] + self[2::4] + self[3::4])

View File

@ -7,8 +7,8 @@
# Copyright © 2021-2022 by shenjackyuanjie 3695888@qq.com # Copyright © 2021-2022 by shenjackyuanjie 3695888@qq.com
# All rights reserved # All rights reserved
# ------------------------------- # -------------------------------
import re
import os import os
import re
import time import time
import atexit import atexit
import inspect import inspect
@ -16,12 +16,13 @@ import threading
from queue import Queue from queue import Queue
from time import strftime from time import strftime
from logging import NOTSET, DEBUG, INFO, WARNING, ERROR, FATAL from logging import NOTSET, DEBUG
from types import FrameType from types import FrameType
from typing import NoReturn, Optional, Type, Union, Dict, Iterable, Any, List from typing import Optional, Type, Union, Dict, Iterable, Any, List
os.system('') os.system('')
# print(os.path.abspath(os.curdir)) # print(os.path.abspath(os.curdir))
# TODO 这个文件就是个大TODO
""" """
如果想要直接使用 logger logging 如果想要直接使用 logger logging
直接调用 logger.debug() 即可 直接调用 logger.debug() 即可
@ -161,8 +162,7 @@ logger_configs = {
LoggingLevel.INFO_t: {'info': '\033[0m'}, LoggingLevel.INFO_t: {'info': '\033[0m'},
LoggingLevel.WARNING_t: {'info': '\033[33m'}, LoggingLevel.WARNING_t: {'info': '\033[33m'},
LoggingLevel.ERROR_t: {'info': '\033[31m'}, LoggingLevel.ERROR_t: {'info': '\033[31m'},
LoggingLevel.FATAL_t: { LoggingLevel.FATAL_t: {'info': '\033[38;2;255;255;0;48;2;120;10;10m', 'logger': '\033[38;2;245;189;230m'}
'info': '\033[38;2;255;255;0;48;2;120;10;10m', 'logger': '\033[38;2;245;189;230m'}
}, },
'fancy_main_color': { 'fancy_main_color': {
# 'file_time': '\033[38;2;201;222;56m', # 'file_time': '\033[38;2;201;222;56m',
@ -455,8 +455,9 @@ class CachedFileHandler(StreamHandlerTemplate):
if by_thread: if by_thread:
with self.time_limit_lock: with self.time_limit_lock:
with open(file=self.file_conf.file_name, mode=self.file_conf.file_mode, with open(file=self.file_conf.file_name, mode=self.file_conf.file_mode,
encoding=self.file_conf.file_encoding) as log_file: encoding=self.file_conf.file_encoding) as log_file:
... while not self.string_queue.empty():
log_file.write(self.string_queue.get())
def write(self, message: str, flush: Optional[bool]) -> bool: def write(self, message: str, flush: Optional[bool]) -> bool:
if not flush: if not flush: