turbopipe 0.0.0__tar.gz → 1.0.0__tar.gz

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.

Potentially problematic release.


This version of turbopipe might be problematic. Click here for more details.

@@ -0,0 +1,2 @@
1
+ turbopipe/include/*.hpp linguist-vendored
2
+ turbopipe/include/*.h linguist-vendored
@@ -0,0 +1,83 @@
1
+ name: release
2
+
3
+ on:
4
+ workflow_dispatch:
5
+ push:
6
+ paths:
7
+ - 'turbopipe/version.py'
8
+
9
+ jobs:
10
+ sdist:
11
+ name: Package source
12
+ runs-on: ubuntu-latest
13
+
14
+ steps:
15
+ - uses: actions/checkout@v4
16
+ - uses: actions/setup-python@v5
17
+
18
+ - name: deps
19
+ run: python -m pip install -U pip wheel build
20
+
21
+ - name: sdist
22
+ run: python -m build --sdist -o package
23
+
24
+ - name: upload
25
+ uses: actions/upload-artifact@v3
26
+ with:
27
+ name: package
28
+ path: package/*.tar.gz
29
+
30
+ wheels:
31
+ name: Build ${{matrix.pyver}} wheels on ${{matrix.os}}
32
+ runs-on: ${{matrix.os}}
33
+ strategy:
34
+ matrix:
35
+ os: [ubuntu-latest, windows-latest, macos-13, macos-14]
36
+ pyver: [cp39, cp310, cp311, cp312]
37
+
38
+ env:
39
+ CIBW_BUILD: ${{matrix.pyver}}-*
40
+ CIBW_ARCHS_LINUX: auto
41
+ CIBW_ARCHS_MACOS: arm64
42
+ CIBW_ARCHS_WINDOWS: auto
43
+ CIBW_SKIP: '*musllinux* *i686* *-win32'
44
+
45
+ steps:
46
+ - uses: actions/checkout@v4
47
+ - uses: actions/setup-python@v5
48
+
49
+ - name: deps
50
+ run: python -m pip install cibuildwheel==2.19.2
51
+
52
+ - name: wheels
53
+ run: python -m cibuildwheel --output-dir package
54
+
55
+ - name: upload
56
+ uses: actions/upload-artifact@v3
57
+ with:
58
+ name: package
59
+ path: package/*.whl
60
+
61
+ publish:
62
+ needs: [sdist, wheels]
63
+ name: Publish to PyPI
64
+ runs-on: ubuntu-latest
65
+
66
+ steps:
67
+ - uses: actions/checkout@v4
68
+ - uses: actions/setup-python@v5
69
+
70
+ - name: Download artifacts
71
+ uses: actions/download-artifact@v3
72
+ with:
73
+ name: package
74
+ path: package
75
+
76
+ - name: deps
77
+ run: python -m pip install -U twine
78
+
79
+ - name: publish
80
+ env:
81
+ TWINE_USERNAME: __token__
82
+ TWINE_PASSWORD: ${{secrets.PYPI_TOKEN}}
83
+ run: twine upload package/*
@@ -0,0 +1,4 @@
1
+ turbopipe.egg*
2
+ build*
3
+ .venv
4
+ *.so
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Gabriel Tremeschin
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,34 @@
1
+ Metadata-Version: 2.1
2
+ Name: turbopipe
3
+ Version: 1.0.0
4
+ Summary: 🌀 Faster ModernGL Buffer inter process data transfers
5
+ Home-page: https://brokensrc.dev
6
+ Author-Email: Tremeschin <29046864+Tremeschin@users.noreply.github.com>
7
+ License: MIT License
8
+
9
+ Copyright (c) 2024 Gabriel Tremeschin
10
+
11
+ Permission is hereby granted, free of charge, to any person obtaining a copy
12
+ of this software and associated documentation files (the "Software"), to deal
13
+ in the Software without restriction, including without limitation the rights
14
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
15
+ copies of the Software, and to permit persons to whom the Software is
16
+ furnished to do so, subject to the following conditions:
17
+
18
+ The above copyright notice and this permission notice shall be included in all
19
+ copies or substantial portions of the Software.
20
+
21
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
22
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
24
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
26
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
27
+ SOFTWARE.
28
+ Project-URL: Issues, https://github.com/BrokenSource/TurboPipe/issues
29
+ Project-URL: Repository, https://github.com/BrokenSource/TurboPipe
30
+ Project-URL: Documentation, https://github.com/BrokenSource/TurboPipe
31
+ Project-URL: Homepage, https://brokensrc.dev
32
+ Requires-Python: >=3.7
33
+ Requires-Dist: moderngl
34
+ Description-Content-Type: text/markdown
@@ -0,0 +1,80 @@
1
+ import contextlib
2
+ import random
3
+ import subprocess
4
+ from typing import Generator
5
+
6
+ import moderngl
7
+ import tqdm
8
+ import turbopipe
9
+
10
+ # User constants
11
+ WIDTH, HEIGHT = (1920, 1080)
12
+ FRAMERATE = 60
13
+ DURATION = 30
14
+
15
+ # Calculate constants
16
+ BYTES_PER_FRAME = (WIDTH * HEIGHT * 3)
17
+ TOTAL_FRAMES = (DURATION * FRAMERATE)
18
+ TOTAL_BYTES = (BYTES_PER_FRAME * TOTAL_FRAMES)
19
+
20
+ # Create ModernGL objects
21
+ ctx = moderngl.create_standalone_context()
22
+ buffer = ctx.buffer(reserve=BYTES_PER_FRAME)
23
+
24
+ # Let's play fair and avoid any OS/Python/Hardware optimizations
25
+ buffer.write(bytearray(random.getrandbits(8) for _ in range(BYTES_PER_FRAME)))
26
+
27
+ # -------------------------------------------------------------------------------------------------|
28
+
29
+ @contextlib.contextmanager
30
+ def FFmpeg() -> Generator[subprocess.Popen, None, None]:
31
+ try:
32
+ ffmpeg = subprocess.Popen([
33
+ "ffmpeg",
34
+ "-hide_banner",
35
+ "-loglevel", "error",
36
+ "-f", "rawvideo",
37
+ "-pix_fmt", "rgb24",
38
+ "-s", f"{WIDTH}x{HEIGHT}",
39
+ "-r", str(FRAMERATE),
40
+ "-i", "-",
41
+ "-f", "null",
42
+ "-", "-y"
43
+ ], stdin=subprocess.PIPE)
44
+
45
+ yield ffmpeg
46
+ finally:
47
+ ffmpeg.stdin.close()
48
+ ffmpeg.wait()
49
+
50
+ # -------------------------------------------------------------------------------------------------|
51
+
52
+ @contextlib.contextmanager
53
+ def Progress():
54
+ with tqdm.tqdm(total=TOTAL_FRAMES, unit="Frame", smoothing=0) as frame_bar, \
55
+ tqdm.tqdm(total=TOTAL_BYTES, unit="B", smoothing=0, unit_scale=True) as byte_bar:
56
+ def next():
57
+ byte_bar.update(BYTES_PER_FRAME)
58
+ frame_bar.update(1)
59
+ yield next
60
+
61
+ # -------------------------------------------------------------------------------------------------|
62
+
63
+ print("\n:: Traditional method\n")
64
+
65
+ with Progress() as progress, FFmpeg() as ffmpeg:
66
+ for frame in range(TOTAL_FRAMES):
67
+ ffmpeg.stdin.write(buffer.read())
68
+ progress()
69
+
70
+ print("\n:: TurboPipe method\n")
71
+
72
+ with Progress() as progress, FFmpeg() as ffmpeg:
73
+ fileno = ffmpeg.stdin.fileno()
74
+
75
+ for frame in range(TOTAL_FRAMES):
76
+ turbopipe.pipe(buffer, fileno)
77
+ progress()
78
+ turbopipe.sync()
79
+
80
+ turbopipe.close()
@@ -0,0 +1,50 @@
1
+ # ----------------------------------------------|
2
+
3
+ project('turbopipe', 'cpp',
4
+ version: run_command(
5
+ ['turbopipe/version.py'],
6
+ check: true
7
+ ).stdout().strip(),
8
+
9
+ default_options: [
10
+ 'warning_level=3',
11
+ 'cpp_std=c++11',
12
+ 'buildtype=release',
13
+ 'optimization=3'
14
+ ]
15
+ )
16
+
17
+ # ----------------------------------------------|
18
+ # Platform flags
19
+
20
+ cpp_args = ['-fpermissive']
21
+
22
+ if host_machine.system() == 'darwin'
23
+ cpp_args += ['-Wno-deprecated-declarations']
24
+ endif
25
+
26
+ # ----------------------------------------------|
27
+ # Source files
28
+
29
+ incdir = include_directories('turbopipe/include')
30
+ source = files('turbopipe/_turbopipe.cpp')
31
+
32
+ # ----------------------------------------------|
33
+
34
+ python = import('python').find_installation()
35
+
36
+ python.extension_module(
37
+ '_turbopipe', source,
38
+ include_directories: incdir,
39
+ cpp_args: cpp_args,
40
+ install: true,
41
+ subdir: 'turbopipe'
42
+ )
43
+
44
+ python.install_sources(
45
+ ['turbopipe/__init__.py'],
46
+ subdir: 'turbopipe',
47
+ pure: false
48
+ )
49
+
50
+ # ----------------------------------------------|
@@ -0,0 +1,19 @@
1
+ [project.urls]
2
+ issues = "https://github.com/BrokenSource/TurboPipe/issues"
3
+ repository = "https://github.com/BrokenSource/TurboPipe"
4
+ documentation = "https://github.com/BrokenSource/TurboPipe"
5
+ homepage = "https://brokensrc.dev"
6
+
7
+ [project]
8
+ name = "turbopipe"
9
+ dynamic = ["version"]
10
+ description = "🌀 Faster ModernGL Buffer inter process data transfers"
11
+ authors = [{name="Tremeschin", email="29046864+Tremeschin@users.noreply.github.com"}]
12
+ readme = "Readme.md"
13
+ license = {file="License.md"}
14
+ dependencies = ["moderngl"]
15
+ requires-python = ">=3.7"
16
+
17
+ [build-system]
18
+ requires = ["meson-python", "ninja"]
19
+ build-backend = "mesonpy"
@@ -0,0 +1,41 @@
1
+ from io import IOBase
2
+
3
+ from moderngl import Buffer
4
+
5
+ from turbopipe import _turbopipe
6
+
7
+
8
+ def pipe(buffer: Buffer, file: IOBase) -> None:
9
+ """
10
+ Pipe the content of a moderngl.Buffer to a file descriptor,
11
+ Fast, threaded and non-blocking. Call `sync()` when done!
12
+
13
+ Usage:
14
+ ```python
15
+ # Assuming `buffer = ctx.buffer(...)`
16
+ # Note: Use as `fbo.read_into(buffer)`
17
+
18
+ # As a open() file
19
+ with open("file.bin", "wb") as file:
20
+ turbopipe.pipe(buffer, file)
21
+
22
+ # As a subprocess
23
+ child = subprocess.Popen(..., stdin=subprocess.PIPE)
24
+ turbopipe.pipe(buffer, child.stdin.fileno())
25
+ ```
26
+ """
27
+ _turbopipe.pipe(buffer.mglo, file)
28
+
29
+ def sync() -> None:
30
+ """Waits for all jobs to finish"""
31
+ _turbopipe.sync()
32
+
33
+ def close() -> None:
34
+ """Syncs and deletes objects"""
35
+ _turbopipe.close()
36
+
37
+ __all__ = [
38
+ "pipe",
39
+ "sync",
40
+ "close"
41
+ ]
@@ -0,0 +1,258 @@
1
+ // ------------------------------------------------------------------------------------------------|
2
+ //
3
+ // TurboPipe - Faster ModernGL Buffer inter process data transfers
4
+ //
5
+ // (c) 2024, Tremeschin, MIT License
6
+ //
7
+ // ------------------------------------------------------------------------------------------------|
8
+
9
+ #define PY_SSIZE_T_CLEAN
10
+ #include <Python.h>
11
+
12
+ #include <thread>
13
+ #include <mutex>
14
+ #include <condition_variable>
15
+ #include <unistd.h>
16
+ #include <iostream>
17
+ #include <unordered_map>
18
+ #include <deque>
19
+ #include <chrono>
20
+ #include <functional>
21
+ #include <set>
22
+
23
+ #include "gl_methods.hpp"
24
+
25
+ #define dict std::unordered_map
26
+ using namespace std;
27
+
28
+ // ------------------------------------------------------------------------------------------------|
29
+ // ModernGL Types - Courtesy of the moderngl package developers (MIT)
30
+
31
+ static PyTypeObject* MGLBuffer_type = nullptr;
32
+
33
+ struct MGLContext;
34
+ struct MGLFramebuffer;
35
+
36
+ struct MGLBuffer {
37
+ PyObject_HEAD
38
+ MGLContext* context;
39
+ int buffer;
40
+ Py_ssize_t size;
41
+ bool dynamic;
42
+ bool released;
43
+ };
44
+
45
+ struct MGLContext {
46
+ PyObject_HEAD
47
+ PyObject * ctx;
48
+ PyObject * extensions;
49
+ MGLFramebuffer * default_framebuffer;
50
+ MGLFramebuffer * bound_framebuffer;
51
+ PyObject * includes;
52
+ int version_code;
53
+ int max_samples;
54
+ int max_integer_samples;
55
+ int max_color_attachments;
56
+ int max_texture_units;
57
+ int default_texture_unit;
58
+ float max_anisotropy;
59
+ int enable_flags;
60
+ int front_face;
61
+ int cull_face;
62
+ int depth_func;
63
+ bool depth_clamp;
64
+ double depth_range[2];
65
+ int blend_func_src;
66
+ int blend_func_dst;
67
+ bool wireframe;
68
+ bool multisample;
69
+ int provoking_vertex;
70
+ float polygon_offset_factor;
71
+ float polygon_offset_units;
72
+ GLMethods gl;
73
+ bool released;
74
+ };
75
+
76
+ // ------------------------------------------------------------------------------------------------|
77
+ // TurboPipe internals
78
+
79
+ struct Work {
80
+ void* map;
81
+ int file;
82
+ size_t size;
83
+
84
+ int hash() {
85
+ return std::hash<int>()(file) ^ std::hash<void*>()(map);
86
+ }
87
+ };
88
+
89
+ class TurboPipe {
90
+ public:
91
+ TurboPipe(): running(true) {}
92
+ ~TurboPipe() {close();}
93
+
94
+ void pipe(MGLBuffer* buffer, int file) {
95
+ const GLMethods& gl = buffer->context->gl;
96
+
97
+ gl.BindBuffer(GL_ARRAY_BUFFER, buffer->buffer);
98
+ void* data = gl.MapBufferRange(GL_ARRAY_BUFFER, 0, buffer->size, GL_MAP_READ_BIT);
99
+ gl.UnmapBuffer(GL_ARRAY_BUFFER);
100
+
101
+ this->_pipe(data, buffer->size, file);
102
+ }
103
+
104
+ void sync() {
105
+ // Wait for all queues to be empty, as they are erased when
106
+ // each thread's writing loop is done, guaranteeing finish
107
+ for (auto& values: queue) {
108
+ while (!values.second.empty()) {
109
+ this_thread::sleep_for(chrono::milliseconds(1));
110
+ }
111
+ }
112
+ }
113
+
114
+ void close() {
115
+ sync();
116
+ running = false;
117
+ signal.notify_all();
118
+ for (auto& pair: threads)
119
+ pair.second.join();
120
+ threads.clear();
121
+ }
122
+
123
+ private:
124
+ dict<int, dict<int, condition_variable>> pending;
125
+ dict<int, deque<Work>> stream;
126
+ dict<int, set<int>> queue;
127
+ dict<int, thread> threads;
128
+ dict<int, mutex> mutexes;
129
+ condition_variable signal;
130
+ bool running;
131
+
132
+ void _pipe(void* data, size_t size, int file) {
133
+ Work work = {data, file, size};
134
+ int hash = work.hash();
135
+
136
+ unique_lock<mutex> lock(mutexes[file]);
137
+
138
+ // Notify this hash is queued, wait if pending
139
+ if (!queue[file].insert(hash).second) {
140
+ pending[file][hash].wait(lock, [this, file, hash] {
141
+ return queue[file].find(hash) == queue[file].end();
142
+ });
143
+ pending[file].erase(hash);
144
+ queue[file].insert(hash);
145
+ }
146
+
147
+ // Add another job to the queue
148
+ stream[file].push_back(work);
149
+ lock.unlock();
150
+
151
+ // Each file descriptor has its own thread
152
+ if (threads.find(file) == threads.end())
153
+ threads[file] = thread(&TurboPipe::worker, this, file);
154
+
155
+ signal.notify_all();
156
+ }
157
+
158
+ void worker(int file) {
159
+ while (this->running) {
160
+ unique_lock<mutex> lock(mutexes[file]);
161
+
162
+ signal.wait(lock, [this, file] {
163
+ return (!stream[file].empty() || !this->running);
164
+ });
165
+
166
+ // Skip on false positives, exit condition
167
+ if (stream[file].empty()) continue;
168
+ if (!this->running) break;
169
+
170
+ // Get the next work item
171
+ Work work = stream[file].front();
172
+ stream[file].pop_front();
173
+ lock.unlock();
174
+
175
+ // Optimization: Write in chunks of 4096 (RAM page size)
176
+ size_t tell = 0;
177
+ while (tell < work.size) {
178
+ size_t chunk = min(work.size - tell, static_cast<size_t>(4096));
179
+ ssize_t written = write(work.file, (char*) work.map + tell, chunk);
180
+ if (written == -1) break;
181
+ tell += written;
182
+ }
183
+
184
+ // Signal work is done
185
+ lock.lock();
186
+ int hash = work.hash();
187
+ pending[file][hash].notify_all();
188
+ queue[file].erase(hash);
189
+ signal.notify_all();
190
+ }
191
+ }
192
+ };
193
+
194
+ // The main and only instance of TurboPipe
195
+ static TurboPipe* turbopipe = nullptr;
196
+
197
+ // ------------------------------------------------------------------------------------------------|
198
+ // End user methods
199
+
200
+ static PyObject* turbopipe_pipe(
201
+ PyObject* Py_UNUSED(self),
202
+ PyObject* args
203
+ ) {
204
+ PyObject* buffer;
205
+ PyObject* file;
206
+ if (!PyArg_ParseTuple(args, "OO", &buffer, &file))
207
+ return NULL;
208
+ turbopipe->pipe((MGLBuffer*) buffer, PyLong_AsLong(file));
209
+ Py_RETURN_NONE;
210
+ }
211
+
212
+ static PyObject* turbopipe_sync(
213
+ PyObject* Py_UNUSED(self),
214
+ PyObject* Py_UNUSED(args)
215
+ ) {
216
+ turbopipe->sync();
217
+ Py_RETURN_NONE;
218
+ }
219
+
220
+ static PyObject* turbopipe_close(
221
+ PyObject* Py_UNUSED(self),
222
+ PyObject* Py_UNUSED(args)
223
+ ) {
224
+ turbopipe->close();
225
+ Py_RETURN_NONE;
226
+ }
227
+
228
+ // ------------------------------------------------------------------------------------------------|
229
+ // Python module definition
230
+
231
+ static void turbopipe_exit() {
232
+ turbopipe->close();
233
+ }
234
+
235
+ static PyMethodDef TurboPipeMethods[] = {
236
+ {"pipe", (PyCFunction) turbopipe_pipe, METH_VARARGS, ""},
237
+ {"sync", (PyCFunction) turbopipe_sync, METH_NOARGS, ""},
238
+ {"close", (PyCFunction) turbopipe_close, METH_NOARGS, ""},
239
+ {NULL, NULL, 0, NULL}
240
+ };
241
+
242
+ static struct PyModuleDef turbopipe_module = {
243
+ PyModuleDef_HEAD_INIT,
244
+ "_turbopipe",
245
+ NULL, -1,
246
+ TurboPipeMethods,
247
+ NULL, NULL, NULL, NULL
248
+ };
249
+
250
+ PyMODINIT_FUNC PyInit__turbopipe(void) {
251
+ PyObject* module = PyModule_Create(&turbopipe_module);
252
+ PyObject* moderngl = PyImport_ImportModule("moderngl");
253
+ PyObject* buffer = PyObject_GetAttrString(moderngl, "Buffer");
254
+ MGLBuffer_type = (PyTypeObject*) buffer;
255
+ turbopipe = new TurboPipe();
256
+ Py_AtExit(turbopipe_exit);
257
+ return module;
258
+ }