pyglet 3.0.dev3__py3-none-any.whl → 3.0.dev4__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) hide show
  1. pyglet/__init__.py +2 -2
  2. pyglet/app/cocoa.py +2 -1
  3. pyglet/customtypes.py +2 -2
  4. pyglet/event.py +10 -2
  5. pyglet/experimental/README.md +26 -1
  6. pyglet/experimental/flaccodec.py +380 -0
  7. pyglet/experimental/geoshader_sprite.py +2 -6
  8. pyglet/experimental/net.py +6 -6
  9. pyglet/experimental/particles.py +38 -0
  10. pyglet/experimental/win32priority.py +68 -0
  11. pyglet/font/dwrite/__init__.py +1 -1
  12. pyglet/font/dwrite/d2d1_lib.py +1 -1
  13. pyglet/font/fontconfig.py +1 -1
  14. pyglet/graphics/__init__.py +39 -7
  15. pyglet/graphics/api/__init__.py +14 -25
  16. pyglet/graphics/api/gl/buffer.py +108 -25
  17. pyglet/graphics/api/gl/draw.py +4 -168
  18. pyglet/graphics/api/gl/global_opengl.py +1 -1
  19. pyglet/graphics/api/gl/shader.py +127 -157
  20. pyglet/graphics/api/gl/sprite.py +99 -0
  21. pyglet/graphics/api/gl/texture.py +28 -11
  22. pyglet/graphics/api/gl/vertexdomain.py +115 -4
  23. pyglet/graphics/api/gl2/buffer.py +3 -4
  24. pyglet/graphics/api/gl2/draw.py +6 -134
  25. pyglet/graphics/api/gl2/global_opengl.py +1 -1
  26. pyglet/graphics/api/gl2/shader.py +64 -9
  27. pyglet/graphics/api/gl2/shapes.py +1 -1
  28. pyglet/graphics/api/gl2/sprite.py +87 -0
  29. pyglet/graphics/api/gl2/vertexdomain.py +3 -2
  30. pyglet/graphics/api/webgl/__init__.py +1 -1
  31. pyglet/graphics/api/webgl/buffer.py +100 -30
  32. pyglet/graphics/api/webgl/draw.py +4 -134
  33. pyglet/graphics/api/webgl/shader.py +126 -152
  34. pyglet/graphics/api/webgl/sprite.py +99 -0
  35. pyglet/graphics/api/webgl/texture.py +2 -0
  36. pyglet/graphics/api/webgl/vertexdomain.py +2 -1
  37. pyglet/graphics/atlas.py +4 -4
  38. pyglet/graphics/buffer.py +27 -0
  39. pyglet/graphics/draw.py +107 -24
  40. pyglet/graphics/instance.py +155 -2
  41. pyglet/graphics/shader.py +287 -11
  42. pyglet/graphics/texture.py +68 -56
  43. pyglet/graphics/vertexdomain.py +169 -8
  44. pyglet/image/__init__.py +12 -12
  45. pyglet/image/animation.py +9 -4
  46. pyglet/image/base.py +8 -9
  47. pyglet/image/codecs/wic.py +1 -1
  48. pyglet/info.py +319 -125
  49. pyglet/input/win32/directinput.py +10 -3
  50. pyglet/lib.py +6 -1
  51. pyglet/libs/darwin/cocoapy/cocoalibs.py +83 -0
  52. pyglet/libs/darwin/cocoapy/runtime.py +38 -17
  53. pyglet/math.py +76 -42
  54. pyglet/media/codecs/base.py +2 -2
  55. pyglet/media/codecs/wave.py +17 -11
  56. pyglet/media/drivers/base.py +15 -8
  57. pyglet/media/drivers/pulse/adaptation.py +2 -2
  58. pyglet/media/drivers/pulse/interface.py +2 -2
  59. pyglet/media/player.py +9 -1
  60. pyglet/media/synthesis.py +3 -3
  61. pyglet/model/__init__.py +313 -54
  62. pyglet/model/codecs/__init__.py +2 -2
  63. pyglet/model/codecs/obj.py +7 -1
  64. pyglet/resource.py +74 -6
  65. pyglet/shapes.py +4 -3
  66. pyglet/sprite.py +292 -11
  67. pyglet/text/__init__.py +2 -2
  68. pyglet/text/caret.py +7 -6
  69. pyglet/window/__init__.py +2 -2
  70. {pyglet-3.0.dev3.dist-info → pyglet-3.0.dev4.dist-info}/METADATA +1 -1
  71. {pyglet-3.0.dev3.dist-info → pyglet-3.0.dev4.dist-info}/RECORD +74 -73
  72. pyglet/experimental/multitexture_sprite.py +0 -541
  73. /pyglet/{image/codecs/wincodec_lib.py → libs/win32/wincodec.py} +0 -0
  74. {pyglet-3.0.dev3.dist-info → pyglet-3.0.dev4.dist-info}/WHEEL +0 -0
  75. {pyglet-3.0.dev3.dist-info → pyglet-3.0.dev4.dist-info}/licenses/LICENSE +0 -0
pyglet/__init__.py CHANGED
@@ -18,7 +18,7 @@ if TYPE_CHECKING:
18
18
  from typing import Any, Callable, ItemsView, Sized
19
19
 
20
20
  #: The release version
21
- version = '3.0.dev3'
21
+ version = '3.0.dev4'
22
22
  __version__ = version
23
23
 
24
24
  MIN_PYTHON_VERSION = 3, 8
@@ -103,7 +103,7 @@ class Options:
103
103
 
104
104
  debug_graphics_batch: bool = False
105
105
  """If ``True``, prints batch information being drawn, including :py:class:`~pyglet.graphics.Group`'s, VertexDomains,
106
- and :py:class:`~pyglet.graphics.Texture` information. This can be useful to see how many Group's are being
106
+ and :py:class:`~pyglet.graphics.texture.Texture` information. This can be useful to see how many Group's are being
107
107
  consolidated."""
108
108
 
109
109
  debug_lib: bool = False
pyglet/app/cocoa.py CHANGED
@@ -6,7 +6,7 @@ import time
6
6
  from pyglet import app
7
7
  from pyglet.app.base import PlatformEventLoop, EventLoop
8
8
  from pyglet.libs.darwin import cocoapy, AutoReleasePool, ObjCSubclass, PyObjectEncoding, ObjCInstance, send_super, \
9
- ObjCClass, get_selector
9
+ ObjCClass, get_selector, set_realtime_thread_policy
10
10
 
11
11
  NSApplication = cocoapy.ObjCClass('NSApplication')
12
12
  NSMenu = cocoapy.ObjCClass('NSMenu')
@@ -161,6 +161,7 @@ class CocoaPlatformEventLoop(PlatformEventLoop):
161
161
 
162
162
  def __init__(self):
163
163
  super().__init__()
164
+ set_realtime_thread_policy()
164
165
  self._timer = None
165
166
 
166
167
  with AutoReleasePool():
pyglet/customtypes.py CHANGED
@@ -22,8 +22,8 @@ MediaTypes = Literal["audio", "video", "image"]
22
22
 
23
23
  Number = Union[int, float]
24
24
 
25
- RGBColor = Tuple[Number, Number, Number]
26
- RGBAColor = Tuple[Number, Number, Number, Number]
25
+ RGBColor = Tuple[int, int, int]
26
+ RGBAColor = Tuple[int, int, int, int]
27
27
 
28
28
  DataTypes = Literal[
29
29
  'f', # float
pyglet/event.py CHANGED
@@ -356,8 +356,16 @@ class EventDispatcher:
356
356
  if not handler:
357
357
  continue
358
358
  if isinstance(handler, WeakMethod):
359
- handler = handler()
360
- assert handler is not None
359
+ weak_handler = handler
360
+ handler = weak_handler()
361
+ if handler is None:
362
+ # Weak handlers can expire before their callback is removed from stack.
363
+ # Skip and remove stale handlers
364
+ if frame.get(event_type) is weak_handler:
365
+ del frame[event_type]
366
+ if not frame and frame in self._event_stack:
367
+ self._event_stack.remove(frame)
368
+ continue
361
369
  try:
362
370
  invoked = True
363
371
  if handler(*args):
@@ -3,4 +3,29 @@ Experimental modules
3
3
 
4
4
  This package contains experimental modules, which are included here for
5
5
  wider testing and feedback. Anything contained within may be broken, refactored,
6
- or removed without notice.
6
+ or removed without notice.
7
+
8
+ A module may belong in `experimental` when it meets one or more of the following criteria:
9
+
10
+ - The API is still being designed.
11
+ - The implementation needs wider testing.
12
+ - The feature depends on external tools, generated assets, or optional workflows.
13
+ - The feature is useful, but too specialized for the core package.
14
+ - The feature introduces complexity that should not be required for normal users.
15
+ - The feature may eventually move into the stable package after review.
16
+
17
+ ## Requirements
18
+
19
+ Each experimental module should include documentation explaining:
20
+
21
+ - What the module solves or does.
22
+ - What tools, files, or assets are required.
23
+ - A minimal example, either as a `"__main__"` execution, or docstring.
24
+
25
+ ## Graduation to stable API
26
+
27
+ An experimental module may be considered for promotion to the stable package when:
28
+
29
+ - The API has proven useful and reasonably stable.
30
+ - The feature has documentation and examples.
31
+ - Maintainers are willing to support it long-term.
@@ -0,0 +1,380 @@
1
+ import re
2
+ import struct
3
+
4
+ from concurrent.futures import ProcessPoolExecutor
5
+
6
+ from pyglet.util import DecodeException
7
+ from pyglet.media.codecs.base import StreamingSource, AudioData, AudioFormat, StaticSource
8
+ from pyglet.media.codecs import MediaDecoder
9
+
10
+
11
+ # This module uses code from Project Nayuki:
12
+ # https://www.nayuki.io/page/simple-flac-implementation
13
+
14
+
15
+ class FLACDecodeException(DecodeException):
16
+ pass
17
+
18
+
19
+ FIXED_PREDICTION_COEFFICIENTS = ((), (1,), (2, -1), (3, -3, 1), (4, -6, 4, -1))
20
+
21
+
22
+ def _initializer(filename, numchannels, sample_size):
23
+ """Function to open a copy of the file locally in each Process."""
24
+ global _file
25
+ global _numchannels
26
+ global _sample_size
27
+ _file = open(filename, 'rb')
28
+ _numchannels = numchannels
29
+ _sample_size = sample_size
30
+
31
+
32
+ def decode_frame(offset):
33
+ try:
34
+ # Read a ton of header fields, and ignore most of them
35
+ numchannels = _numchannels
36
+ sample_size = _sample_size
37
+ _file.seek(offset)
38
+ inp = BitInputStream(_file)
39
+
40
+ temp = inp.read_byte()
41
+ if temp == -1:
42
+ return False
43
+ sync = temp << 6 | inp.read_uint(6)
44
+ if sync != 0x3FFE:
45
+ raise FLACDecodeException("Sync code expected")
46
+
47
+ inp.read_uint(1)
48
+ inp.read_uint(1)
49
+ blocksizecode = inp.read_uint(4)
50
+ sampleratecode = inp.read_uint(4)
51
+ chanasgn = inp.read_uint(4)
52
+ inp.read_uint(3)
53
+ inp.read_uint(1)
54
+
55
+ temp = inp.read_uint(8)
56
+ while temp >= 0b11000000:
57
+ inp.read_uint(8)
58
+ temp = (temp << 1) & 0xFF
59
+
60
+ if blocksizecode == 1:
61
+ blocksize = 192
62
+ elif 2 <= blocksizecode <= 5:
63
+ blocksize = 576 << blocksizecode - 2
64
+ elif blocksizecode == 6:
65
+ blocksize = inp.read_uint(8) + 1
66
+ elif blocksizecode == 7:
67
+ blocksize = inp.read_uint(16) + 1
68
+ elif 8 <= blocksizecode <= 15:
69
+ blocksize = 256 << (blocksizecode - 8)
70
+ else:
71
+ raise FLACDecodeException("Invalid Blocksize")
72
+
73
+ if sampleratecode == 12:
74
+ inp.read_uint(8)
75
+ elif sampleratecode in (13, 14):
76
+ inp.read_uint(16)
77
+
78
+ crc8 = inp.read_byte()
79
+
80
+ # Decode each channel's subframe, then skip footer
81
+ if 0 <= chanasgn <= 7:
82
+ samples = [decode_subframe(inp, blocksize, sample_size) for _ in range(chanasgn + 1)]
83
+ elif 8 <= chanasgn <= 10:
84
+ temp0 = decode_subframe(inp, blocksize, sample_size + (1 if (chanasgn == 9) else 0))
85
+ temp1 = decode_subframe(inp, blocksize, sample_size + (0 if (chanasgn == 9) else 1))
86
+ if chanasgn == 8:
87
+ for i in range(blocksize):
88
+ temp1[i] = temp0[i] - temp1[i]
89
+ elif chanasgn == 9:
90
+ for i in range(blocksize):
91
+ temp0[i] += temp1[i]
92
+ elif chanasgn == 10:
93
+ for i in range(blocksize):
94
+ side = temp1[i]
95
+ right = temp0[i] - (side >> 1)
96
+ temp1[i] = right
97
+ temp0[i] = right + side
98
+ samples = [temp0, temp1]
99
+ else:
100
+ raise FLACDecodeException("Reserved channel assignment")
101
+
102
+ inp.align_to_byte()
103
+ inp.read_byte()
104
+ inp.read_byte()
105
+
106
+ # Write the decoded samples
107
+ fmt = str(blocksize * numchannels) + 'h'
108
+ if sample_size == 8:
109
+ return struct.pack(fmt, *(samples[i][j] + 128 for j in range(blocksize) for i in range(numchannels)))
110
+ else:
111
+ return struct.pack(fmt, *(samples[i][j] for j in range(blocksize) for i in range(numchannels)))
112
+ except:
113
+ return b""
114
+
115
+
116
+ def decode_subframe(inp, blocksize, sampledepth):
117
+ inp.read_uint(1)
118
+ subframe_type = inp.read_uint(6)
119
+ shift = inp.read_uint(1)
120
+ if shift == 1:
121
+ while inp.read_uint(1) == 0:
122
+ shift += 1
123
+ sampledepth -= shift
124
+
125
+ if subframe_type == 0: # Constant coding
126
+ result = [inp.read_signed_int(sampledepth)] * blocksize
127
+ elif subframe_type == 1: # Verbatim coding
128
+ result = [inp.read_signed_int(sampledepth) for _ in range(blocksize)]
129
+ elif 8 <= subframe_type <= 12:
130
+ result = decode_fixed_prediction_subframe(inp, subframe_type - 8, blocksize, sampledepth)
131
+ elif 32 <= subframe_type <= 63:
132
+ result = decode_linear_predictive_coding_subframe(inp, subframe_type - 31, blocksize, sampledepth)
133
+ else:
134
+ raise FLACDecodeException("Reserved subframe type")
135
+ return [(v << shift) for v in result]
136
+
137
+
138
+ def decode_fixed_prediction_subframe(inp, predorder, blocksize, sampledepth):
139
+ result = [inp.read_signed_int(sampledepth) for _ in range(predorder)]
140
+ decode_residuals(inp, blocksize, result)
141
+ restore_linear_prediction(result, FIXED_PREDICTION_COEFFICIENTS[predorder], 0)
142
+ return result
143
+
144
+
145
+ def decode_linear_predictive_coding_subframe(inp, lpcorder, blocksize, sampledepth):
146
+ result = [inp.read_signed_int(sampledepth) for _ in range(lpcorder)]
147
+ precision = inp.read_uint(4) + 1
148
+ shift = inp.read_signed_int(5)
149
+ coefs = [inp.read_signed_int(precision) for _ in range(lpcorder)]
150
+ decode_residuals(inp, blocksize, result)
151
+ restore_linear_prediction(result, coefs, shift)
152
+ return result
153
+
154
+
155
+ def decode_residuals(inp, blocksize, result):
156
+ method = inp.read_uint(2)
157
+ if method >= 2:
158
+ raise FLACDecodeException("Reserved residual coding method")
159
+ parambits = [4, 5][method]
160
+ escapeparam = [0xF, 0x1F][method]
161
+
162
+ partitionorder = inp.read_uint(4)
163
+ numpartitions = 1 << partitionorder
164
+ if blocksize % numpartitions != 0:
165
+ raise FLACDecodeException("Block size not divisible by number of Rice partitions")
166
+
167
+ for i in range(numpartitions):
168
+ count = blocksize >> partitionorder
169
+ if i == 0:
170
+ count -= len(result)
171
+ param = inp.read_uint(parambits)
172
+ if param < escapeparam:
173
+ result.extend(inp.read_rice_signed_int(param) for _ in range(count))
174
+ else:
175
+ numbits = inp.read_uint(5)
176
+ result.extend(inp.read_signed_int(numbits) for _ in range(count))
177
+
178
+
179
+ def restore_linear_prediction(result, coefs, shift):
180
+ for i in range(len(coefs), len(result)):
181
+ result[i] += sum((result[i - 1 - j] * c) for (j, c) in enumerate(coefs)) >> shift
182
+
183
+
184
+ class BitInputStream:
185
+ __slots__ = 'inp', 'bitbuffer', 'bitbufferlen'
186
+
187
+ def __init__(self, inp):
188
+ self.inp = inp
189
+ self.bitbuffer = 0
190
+ self.bitbufferlen = 0
191
+
192
+ def align_to_byte(self):
193
+ self.bitbufferlen -= self.bitbufferlen % 8
194
+
195
+ def read_byte(self):
196
+ if self.bitbufferlen >= 8:
197
+ return self.read_uint(8)
198
+ else:
199
+ result = self.inp.read(1)
200
+ if len(result) == 0:
201
+ return -1
202
+ return result[0]
203
+
204
+ def read_uint(self, n):
205
+ bitbuffer = self.bitbuffer
206
+ bitbufferlen = self.bitbufferlen
207
+
208
+ while bitbufferlen < n:
209
+ temp = self.inp.read(1)
210
+ if len(temp) == 0:
211
+ raise EOFError()
212
+ temp = temp[0]
213
+ bitbuffer = (bitbuffer << 8) | temp
214
+ bitbufferlen += 8
215
+ bitbufferlen -= n
216
+ result = (bitbuffer >> bitbufferlen) & ((1 << n) - 1)
217
+ bitbuffer &= (1 << bitbufferlen) - 1
218
+
219
+ self.bitbuffer = bitbuffer
220
+ self.bitbufferlen = bitbufferlen
221
+ return result
222
+
223
+ def read_signed_int(self, n):
224
+ temp = self.read_uint(n)
225
+ temp -= (temp >> (n - 1)) << n
226
+ return temp
227
+
228
+ def read_rice_signed_int(self, param):
229
+ read_uint = self.read_uint
230
+ val = 0
231
+ while read_uint(1) == 0:
232
+ val += 1
233
+ val = (val << param) | read_uint(param)
234
+ return (val >> 1) ^ -(val & 1)
235
+
236
+ def close(self):
237
+ self.inp.close()
238
+
239
+
240
+ def _find_frame_indicies(data):
241
+ magic1 = struct.pack('BB', int('11111111', 2), int('11111000', 2))
242
+ magic2 = struct.pack('BB', int('11111111', 2), int('11111001', 2))
243
+ indices1 = [m.start() for m in re.finditer(magic1, data)]
244
+ indices2 = [m.start() for m in re.finditer(magic2, data)]
245
+ indices = indices1 + indices2
246
+ indices.sort()
247
+ return indices
248
+
249
+
250
+ #########################################
251
+ # Source class:
252
+ #########################################
253
+ class FLACSource(StreamingSource):
254
+ def __init__(self, filename, file=None):
255
+ if file is None:
256
+ file = open(filename, 'rb')
257
+
258
+ self.frame_indices = _find_frame_indicies(file.read())
259
+ self.frame_index = 0
260
+ file.seek(0)
261
+ self._file = BitInputStream(file)
262
+ self._filename = filename
263
+
264
+ if file.read(4) != b'fLaC':
265
+ raise FLACDecodeException("Does not appear to be a FLAC file.")
266
+
267
+ samplerate = numchannels = bits_per_sample = numsamples = None
268
+
269
+ last = False
270
+ while not last:
271
+ last = self._file.read_uint(1) != 0
272
+ block_type = self._file.read_uint(7)
273
+ length = self._file.read_uint(24)
274
+ if block_type == 0: # Stream info block
275
+ min_block_size = self._file.read_uint(16) # in samples
276
+ max_block_size = self._file.read_uint(16) # in samples
277
+ min_frame_size = self._file.read_uint(24) # in bytes, 0 == unknown
278
+ max_frame_size = self._file.read_uint(24) # in bytes, 0 == unknown
279
+ samplerate = self._file.read_uint(20)
280
+ numchannels = self._file.read_uint(3) + 1
281
+ bits_per_sample = self._file.read_uint(5) + 1
282
+ numsamples = self._file.read_uint(36)
283
+ self._file.read_uint(128)
284
+ else: # Skip other metadata blocks
285
+ for i in range(length):
286
+ self._file.read_byte()
287
+
288
+ for offset in self.frame_indices:
289
+ if offset < self._file.inp.tell():
290
+ self.frame_indices.remove(offset)
291
+
292
+ if not samplerate:
293
+ raise FLACDecodeException("Stream info metadata block absent")
294
+ if bits_per_sample % 8 != 0:
295
+ raise FLACDecodeException("Sample depth {} not supported".format(bits_per_sample))
296
+
297
+ self._numsamples = numsamples
298
+ self._duration = float(numsamples) / samplerate / 60
299
+ self.audio_format = AudioFormat(numchannels, bits_per_sample, samplerate)
300
+
301
+ self.executor = ProcessPoolExecutor(max_workers=3, initializer=_initializer,
302
+ initargs=(filename, numchannels, bits_per_sample))
303
+
304
+ def __del__(self):
305
+ self._file.close()
306
+
307
+ def get_audio_data(self, num_bytes, compensation_time=0.0):
308
+
309
+ data = b''
310
+
311
+ while not data and (self.frame_index <= len(self.frame_indices)):
312
+ offset = self.frame_indices[self.frame_index]
313
+ future = self.executor.submit(decode_frame, offset)
314
+ data += future.result()
315
+ self.frame_index += 1
316
+
317
+ if not data:
318
+ return None
319
+
320
+ return AudioData(data, len(data), timestamp=0.1, duration=0.5, events=[])
321
+
322
+ def save(self, filename="output.wav"):
323
+ with open(filename, 'wb') as out:
324
+ numsamples = self._numsamples
325
+ numchannels = self.audio_format.channels
326
+ samplerate = self.audio_format.sample_rate
327
+ sample_size = self.audio_format.sample_size
328
+
329
+ # Start writing WAV file headers
330
+ sampledatalen = numsamples * numchannels * (sample_size // 8)
331
+
332
+ out.write(b"RIFF")
333
+ out.write(struct.pack("<I", sampledatalen + 36))
334
+ out.write(b"WAVE")
335
+ out.write(b"fmt ")
336
+ out.write(struct.pack("<IHHIIHH",
337
+ 16,
338
+ 0x0001,
339
+ numchannels,
340
+ samplerate,
341
+ samplerate * numchannels * (sample_size // 8),
342
+ numchannels * (sample_size // 8),
343
+ sample_size))
344
+ out.write(b"data")
345
+ out.write(struct.pack("<I", sampledatalen))
346
+
347
+ import time
348
+ start = time.time()
349
+ futures = [self.executor.submit(decode_frame, offset) for offset in self.frame_indices]
350
+ print("elapsed future making", time.time() - start)
351
+
352
+ while futures:
353
+ data = futures.pop(0).result()
354
+ out.write(data)
355
+
356
+ def seek(self, timestamp):
357
+ pass
358
+
359
+
360
+ #########################################
361
+ # Decoder class:
362
+ #########################################
363
+ class FLACDecoder(MediaDecoder):
364
+
365
+ def get_file_extensions(self):
366
+ return '.flac',
367
+
368
+ def decode(self, file, filename, streaming=True):
369
+ if streaming:
370
+ return FLACSource(filename, file)
371
+ else:
372
+ return StaticSource(FLACSource(filename, file))
373
+
374
+
375
+ def get_decoders():
376
+ return [FLACDecoder()]
377
+
378
+
379
+ def get_encoders():
380
+ return []
@@ -245,7 +245,7 @@ class Sprite(event.EventDispatcher):
245
245
  """Create a sprite.
246
246
 
247
247
  :Parameters:
248
- `img` : `~pyglet.image.AbstractImage` or `~pyglet.image.Animation`
248
+ `img` :
249
249
  Image or animation to display.
250
250
  `x` : int
251
251
  X coordinate of the sprite.
@@ -418,11 +418,7 @@ class Sprite(event.EventDispatcher):
418
418
 
419
419
  @property
420
420
  def image(self):
421
- """Image or animation to display.
422
-
423
- :type: :py:class:`~pyglet.image.AbstractImage` or
424
- :py:class:`~pyglet.image.Animation`
425
- """
421
+ """Image or animation to display."""
426
422
  if self._animation:
427
423
  return self._animation
428
424
  return self._texture
@@ -1,4 +1,4 @@
1
- """Experimental networking
1
+ """Experimental networking.
2
2
 
3
3
  This module contains experiments in making user-friendly Server and Client
4
4
  classes that integrate with pyglet's event system. These are very basic,
@@ -56,7 +56,7 @@ _debug_net = debug_print('debug_net')
56
56
 
57
57
 
58
58
  class Client(_EventDispatcher):
59
- def __init__(self, address, port):
59
+ def __init__(self, address: str, port: int):
60
60
  """Create a Client connection to a Server."""
61
61
  self._socket = _socket.create_connection((address, port))
62
62
  self._address = address
@@ -70,7 +70,7 @@ class Client(_EventDispatcher):
70
70
 
71
71
  self._sentinal = object() # poison pill
72
72
 
73
- def close(self):
73
+ def close(self) -> None:
74
74
  """Close the connection."""
75
75
  self._queue.put(self._sentinal)
76
76
  self._socket.shutdown(1)
@@ -78,15 +78,15 @@ class Client(_EventDispatcher):
78
78
  self._terminate.set()
79
79
  self.dispatch_event('on_disconnect', self)
80
80
 
81
- def send(self, message):
81
+ def send(self, message: bytes) -> None:
82
82
  """Queue a message to send.
83
83
 
84
84
  Put a string of bytes into the queue to send.
85
85
  raises a `ConnectionError` if the connection
86
86
  has been closed or dropped.
87
87
 
88
- :Parameters:
89
- `message` : bytes
88
+ Args:
89
+ message:
90
90
  A string of bytes to send.
91
91
  """
92
92
  if self._terminate.is_set():
@@ -393,3 +393,41 @@ class ParticleManager:
393
393
  batch=self._batch, group=self._group)
394
394
  pyglet.clock.schedule_once(self._delete_callback, self.lifespan, emitter)
395
395
  return emitter
396
+
397
+
398
+ if __name__ == "__main__":
399
+ window = pyglet.window.Window(960, 540, caption="ParticleManager Demo", resizable=True)
400
+
401
+ batch = graphics.Batch()
402
+
403
+ label = pyglet.text.Label("Click and drag.", x=5, y=5, batch=batch)
404
+
405
+ particle_img = image.SolidColorImagePattern((255, 255, 255, 255)).create_image(8, 8)
406
+ manager = ParticleManager(
407
+ particle_img,
408
+ lifespan=1.0,
409
+ count=12,
410
+ velocity=(100.0, 80.0),
411
+ spread=(0.30, 0.30),
412
+ color_start=(255, 200, 80, 220),
413
+ color_end=(255, 40, 20, 0),
414
+ scale_start=(0.4, 0.4),
415
+ scale_end=(1.2, 1.2),
416
+ rotation=0.0,
417
+ batch=batch,
418
+ )
419
+
420
+ @window.event
421
+ def on_draw():
422
+ window.clear()
423
+ batch.draw()
424
+
425
+ @window.event
426
+ def on_mouse_press(x, y, button, modifiers):
427
+ manager.create_emitter(x, y)
428
+
429
+ @window.event
430
+ def on_mouse_drag(x, y, dx, dy, buttons, modifiers):
431
+ manager.create_emitter(x, y)
432
+
433
+ pyglet.app.run()
@@ -0,0 +1,68 @@
1
+ # Utilities to patch Pyglet to provide more precise event timing on Windows.
2
+ # HighTimerResolution is generally the most important of the bunch,
3
+ # since most 60+fps games in Windows will not work right without
4
+ # changing the timer resolution for the app
5
+
6
+ import pyglet
7
+ import ctypes
8
+
9
+ try:
10
+ winmm = ctypes.windll.winmm
11
+ k32 = ctypes.windll.kernel32
12
+ except AttributeError:
13
+ raise NotImplementedError("This is not a Win32 compatible platform.")
14
+
15
+
16
+ class HighTimerResolution:
17
+ """
18
+ A context manager to adjust the main Win32 platform event loop.
19
+
20
+ Events inside the context manager will use the specified resolution of the
21
+ multimedia timer to provide proper timings for 60fps+ framerates.
22
+ On exiting the context manager, the default resolution timer is restored.
23
+
24
+ Most 60fps games can use a resolution of 4, but you may need to go as low
25
+ as 1 to get the most consistent results.
26
+
27
+ Example:
28
+
29
+ with HighTimerResolution():
30
+ pyglet.app.run()
31
+
32
+ """
33
+
34
+ def __init__(self, resolution=1):
35
+ self.resolution = resolution
36
+
37
+ def __enter__(self):
38
+ winmm.timeBeginPeriod(self.resolution)
39
+
40
+ def __exit__(self, *a):
41
+ winmm.timeEndPeriod()
42
+
43
+
44
+ def pin_thread(pin_thread_mask=0x01):
45
+ """
46
+ Pin the main Pyglet app thread to one or more cores by way of a thread affinity mask.
47
+ The default thread mask is 1.
48
+ """
49
+ k32.SetThreadAffinityMask(
50
+ pyglet.app.platform_event_loop._event_thread, pin_thread_mask
51
+ )
52
+
53
+
54
+ def pin_process():
55
+ """
56
+ Pins the entire Python interpreter process to the first CPU core.
57
+ This may slightly improve performance due to affinity.
58
+ """
59
+ k32.SetProcessAffinityMask(k32.GetCurrentProcess(), 1)
60
+
61
+
62
+ def raise_priority(
63
+ priority_class=0x080,
64
+ ):
65
+ """
66
+ Raises the process priority to High. Other priorities can be provided.
67
+ """
68
+ k32.SetPriorityClass(-1, priority_class)
@@ -108,7 +108,7 @@ from pyglet.font.dwrite.dwrite_lib import (
108
108
  DWRITE_INFORMATIONAL_STRING_FULL_NAME,
109
109
  )
110
110
  from pyglet.font.harfbuzz import get_harfbuzz_shaped_glyphs, get_resource_from_dw_font, harfbuzz_available
111
- from pyglet.image.codecs.wincodec_lib import GUID_WICPixelFormat32bppPBGRA, IWICBitmap
111
+ from pyglet.libs.win32.wincodec import GUID_WICPixelFormat32bppPBGRA, IWICBitmap
112
112
  from pyglet.libs.win32 import UINT16, UINT32, UINT64, com
113
113
  from pyglet.libs.win32 import _kernel32 as kernel32
114
114
  from pyglet.libs.win32.constants import (
@@ -28,7 +28,7 @@ from pyglet.font.dwrite.dwrite_lib import (
28
28
  IDWriteTextFormat,
29
29
  IDWriteTextLayout,
30
30
  )
31
- from pyglet.image.codecs.wincodec_lib import IWICBitmap, IWICBitmapSource
31
+ from pyglet.libs.win32.wincodec import IWICBitmap, IWICBitmapSource
32
32
  from pyglet.libs.win32 import c_void, com
33
33
  from pyglet.libs.win32.types import BYTE, UINT32, UINT64
34
34
 
pyglet/font/fontconfig.py CHANGED
@@ -196,7 +196,7 @@ class FontConfig:
196
196
  return FontConfigSearchPattern(self._fontconfig)
197
197
 
198
198
  def style_from_face(self, font_face: FT_Face):
199
- pattern = self._fontconfig.FcFreeTypeQueryFace(font_face, None, 0, None)
199
+ pattern = self._fontconfig.FcFreeTypeQueryFace(font_face, b'', 0, None)
200
200
  result = FontConfigSearchResult(self._fontconfig, pattern)
201
201
  return result.weight, result.italic, result.stretch
202
202