Add design principles

This commit is contained in:
Nguyễn Gia Phong 2020-03-24 15:50:13 +07:00
parent 9265cec923
commit cb2601c7f5
18 changed files with 659 additions and 33 deletions

BIN
doctrees/design.doctree Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,185 @@
Design Principles
=================
In this section, we will discuss a few design principles in order to write
a safe, efficient, easy-to-use and extendable 3D audio library for Python,
by wrapping existing functionalities from the C++ API alure_.
This part of the documentation assumes its reader are at least familiar with
Cython, Python and C++11.
.. _alure: https://github.com/kcat/alure
.. _impl-idiom:
The Impl Idiom
--------------
*Not to be confused with* `the pimpl idiom`_.
For memory-safety, whenever possible, we rely on Cython for allocation and
deallocation of C++ objects. To do this, the nullary constructor needs to be
(re-)declared in Cython, e.g.
.. code-block:: cython
cdef extern from 'foobar.h' namespace 'foobar':
cdef cppclass Foo:
Foo()
float meth(size_t crack) except +
...
The Cython extension type can then be declared as follows
.. code-block:: cython
cdef class Bar:
cdef Foo impl
def __init__(self, *args, **kwargs):
self.impl = ...
@staticmethod
def from_baz(baz: Baz) -> Bar:
bar = Bar.__new__(Bar)
bar.impl = ...
return bar
def meth(self, crack: int) -> float:
return self.impl.meth(crack)
.. _`the pimpl idiom`: https://wiki.c2.com/?PimplIdiom
The Modern Python
-----------------
One of the goal of palace is to create a Pythonic, i.e. intuitive and concise,
interface. To achieve this, we try to make use of some modern Python features,
which not only allow users to adopt palace with ease, but also make their
programs more readable and less error-prone.
.. _getter-setter:
Property Attributes
^^^^^^^^^^^^^^^^^^^
A large proportion of alure API are getters/setter methods. In Python,
it is a good practice to use property_ to abstract these calls, and thus make
the interface more natural with attribute-like referencing and assignments.
Due to implementation details, Cython has to hijack the ``@property`` decorator
to make it work for read-write properties. Unfortunately, the Cython-generated
descriptors do not play very well with other builtin decorators, thus in some
cases, it is recommended to alias the call to ``property`` as follows
.. code-block:: python
getter = property
setter = lambda fset: property(fset=fset, doc=fset.__doc__)
Then ``@getter`` and ``@setter`` can be used to decorate read-only and
write-only properties, respectively, without any trouble even if other
decorators are used for the same extension type method.
.. _property: https://docs.python.org/3/library/functions.html#property
Context Managers
^^^^^^^^^^^^^^^^
The alure API defines many objects that need manual tear-down in
a particular order. Instead of trying to be clever and perform automatic
clean-ups at garbage collection, we should put the user in control.
To quote *The Zen of Python*,
| If the implementation is hard to explain, it's a bad idea.
| If the implementation is easy to explain, it may be a good idea.
With that being said, it does not mean we do not provide any level of
abstraction. A simplified case in point would be
.. code-block:: cython
cdef class Device:
cdef alure.Device impl
def __init__(self, name: str = '') -> None:
self.impl = devmgr.open_playback(name)
def __enter__(self) -> Device:
return self
def __exit__(self, *exc) -> Optional[bool]:
self.close()
def close(self) -> None:
self.impl.close()
Now if the ``with`` statement is used, it will make sure the device
will be closed, regardless of whatever may happen within the inner block
.. code-block:: python
with Device() as dev:
...
as it is equivalent to
.. code-block:: python
dev = Device()
try:
...
finally:
dev.close()
Other than closure/destruction of objects, typical uses of `context managers`__
also include saving and restoring various kinds of global state (as seen in
:py:class:`palace.Context`), locking and unlocking resources, etc.
__ https://docs.python.org/3/reference/datamodel.html#context-managers
The Double Reference
--------------------
While wrapping C++ interfaces, :ref:`the impl idiom <impl-idiom>` might not
be adequate, since the derived Python methods need to be callable from C++.
Luckily, Cython can handle Python objects within C++ classes just fine,
although we'll need to handle the reference count ourselves, e.g.
.. code-block:: cython
cdef cppclass CppDecoder(alure.BaseDecoder):
Decoder pyo
__init__(Decoder decoder):
this.pyo = decoder
Py_INCREF(pyo)
__dealloc__():
Py_DECREF(pyo)
bool seek(uint64_t pos):
return pyo.seek(pos)
With this being done, we can now write the wrapper as simply as
.. code-block:: cython
cdef class BaseDecoder:
cdef shared_ptr[alure.Decoder] pimpl
def __cinit__(self, *args, **kwargs) -> None:
self.pimpl = shared_ptr[alure.Decoder](new CppDecoder(self))
def seek(pos: int) -> bool:
...
Because ``__cinit__`` is called by ``__new__``, any Python class derived
from ``BaseDecoder`` will be exposed to C++ as an attribute of ``CppDecoder``.
Effectively, this means the users can have the alure API calling their
inherited Python object as naturally as if palace is implemented in pure Python.
In practice, :py:class:`palace.BaseDecoder` will also need to take into account
other guarding mechanisms like :py:class:`abc.ABC`. Due to Cython limitations,
implementation as a pure Python class and :ref:`aliasing <getter-setter>` of
``@getter``/``@setter`` should be considered.

View File

@ -1,16 +1,12 @@
.. palace documentation master file, created by
sphinx-quickstart on Sun Jan 5 19:56:04 2020.
You can adapt this file completely to your liking, but it should at least
contain the root `toctree` directive.
Welcome to palace's documentation!
==================================
Welcome to our palace!
======================
.. toctree::
:maxdepth: 2
:caption: Contents:
installation
design
reference

View File

@ -1,7 +1,5 @@
Reference
=========
.. toctree::
.. automodule:: palace
:members:

263
html/design.html Normal file
View File

@ -0,0 +1,263 @@
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta charset="utf-8" />
<title>Design Principles &#8212; palace 0.0.12 documentation</title>
<link rel="stylesheet" href="_static/alabaster.css" type="text/css" />
<link rel="stylesheet" href="_static/pygments.css" type="text/css" />
<script id="documentation_options" data-url_root="./" src="_static/documentation_options.js"></script>
<script src="_static/jquery.js"></script>
<script src="_static/underscore.js"></script>
<script src="_static/doctools.js"></script>
<script src="_static/language_data.js"></script>
<link rel="index" title="Index" href="genindex.html" />
<link rel="search" title="Search" href="search.html" />
<link rel="next" title="Reference" href="reference.html" />
<link rel="prev" title="Installation" href="installation.html" />
<link rel="stylesheet" href="_static/custom.css" type="text/css" />
<meta name="viewport" content="width=device-width, initial-scale=0.9, maximum-scale=0.9" />
</head><body>
<div class="document">
<div class="documentwrapper">
<div class="bodywrapper">
<div class="body" role="main">
<div class="section" id="design-principles">
<h1>Design Principles<a class="headerlink" href="#design-principles" title="Permalink to this headline"></a></h1>
<p>In this section, we will discuss a few design principles in order to write
a safe, efficient, easy-to-use and extendable 3D audio library for Python,
by wrapping existing functionalities from the C++ API <a class="reference external" href="https://github.com/kcat/alure">alure</a>.</p>
<p>This part of the documentation assumes its reader are at least familiar with
Cython, Python and C++11.</p>
<div class="section" id="the-impl-idiom">
<span id="impl-idiom"></span><h2>The Impl Idiom<a class="headerlink" href="#the-impl-idiom" title="Permalink to this headline"></a></h2>
<p><em>Not to be confused with</em> <a class="reference external" href="https://wiki.c2.com/?PimplIdiom">the pimpl idiom</a>.</p>
<p>For memory-safety, whenever possible, we rely on Cython for allocation and
deallocation of C++ objects. To do this, the nullary constructor needs to be
(re-)declared in Cython, e.g.</p>
<div class="highlight-cython notranslate"><div class="highlight"><pre><span></span><span class="k">cdef</span> <span class="kr">extern</span> <span class="k">from</span> <span class="s">&#39;foobar.h&#39;</span> <span class="n">namespace</span> <span class="s">&#39;foobar&#39;</span><span class="p">:</span>
<span class="k">cdef</span> <span class="kt">cppclass</span> <span class="nf">Foo</span><span class="p">:</span>
<span class="n">Foo</span><span class="p">()</span>
<span class="nb">float</span> <span class="n">meth</span><span class="p">(</span><span class="n">size_t</span> <span class="n">crack</span><span class="p">)</span> <span class="k">except</span> <span class="o">+</span>
<span class="o">...</span>
</pre></div>
</div>
<p>The Cython extension type can then be declared as follows</p>
<div class="highlight-cython notranslate"><div class="highlight"><pre><span></span><span class="k">cdef</span> <span class="k">class</span> <span class="nf">Bar</span><span class="p">:</span>
<span class="k">cdef</span> <span class="kt">Foo</span> <span class="nf">impl</span>
<span class="k">def</span> <span class="nf">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">):</span>
<span class="bp">self</span><span class="o">.</span><span class="n">impl</span> <span class="o">=</span> <span class="o">...</span>
<span class="nd">@staticmethod</span>
<span class="k">def</span> <span class="nf">from_baz</span><span class="p">(</span><span class="n">baz</span><span class="p">:</span> <span class="n">Baz</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">Bar</span><span class="p">:</span>
<span class="n">bar</span> <span class="o">=</span> <span class="n">Bar</span><span class="o">.</span><span class="n">__new__</span><span class="p">(</span><span class="n">Bar</span><span class="p">)</span>
<span class="n">bar</span><span class="o">.</span><span class="n">impl</span> <span class="o">=</span> <span class="o">...</span>
<span class="k">return</span> <span class="n">bar</span>
<span class="k">def</span> <span class="nf">meth</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">crack</span><span class="p">:</span> <span class="nb">int</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">float</span><span class="p">:</span>
<span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">impl</span><span class="o">.</span><span class="n">meth</span><span class="p">(</span><span class="n">crack</span><span class="p">)</span>
</pre></div>
</div>
</div>
<div class="section" id="the-modern-python">
<h2>The Modern Python<a class="headerlink" href="#the-modern-python" title="Permalink to this headline"></a></h2>
<p>One of the goal of palace is to create a Pythonic, i.e. intuitive and concise,
interface. To achieve this, we try to make use of some modern Python features,
which not only allow users to adopt palace with ease, but also make their
programs more readable and less error-prone.</p>
<div class="section" id="property-attributes">
<span id="getter-setter"></span><h3>Property Attributes<a class="headerlink" href="#property-attributes" title="Permalink to this headline"></a></h3>
<p>A large proportion of alure API are getters/setter methods. In Python,
it is a good practice to use <a class="reference external" href="https://docs.python.org/3/library/functions.html#property">property</a> to abstract these calls, and thus make
the interface more natural with attribute-like referencing and assignments.</p>
<p>Due to implementation details, Cython has to hijack the <code class="docutils literal notranslate"><span class="pre">&#64;property</span></code> decorator
to make it work for read-write properties. Unfortunately, the Cython-generated
descriptors do not play very well with other builtin decorators, thus in some
cases, it is recommended to alias the call to <code class="docutils literal notranslate"><span class="pre">property</span></code> as follows</p>
<div class="highlight-python notranslate"><div class="highlight"><pre><span></span><span class="n">getter</span> <span class="o">=</span> <span class="nb">property</span>
<span class="n">setter</span> <span class="o">=</span> <span class="k">lambda</span> <span class="n">fset</span><span class="p">:</span> <span class="nb">property</span><span class="p">(</span><span class="n">fset</span><span class="o">=</span><span class="n">fset</span><span class="p">,</span> <span class="n">doc</span><span class="o">=</span><span class="n">fset</span><span class="o">.</span><span class="vm">__doc__</span><span class="p">)</span>
</pre></div>
</div>
<p>Then <code class="docutils literal notranslate"><span class="pre">&#64;getter</span></code> and <code class="docutils literal notranslate"><span class="pre">&#64;setter</span></code> can be used to decorate read-only and
write-only properties, respectively, without any trouble even if other
decorators are used for the same extension type method.</p>
</div>
<div class="section" id="context-managers">
<h3>Context Managers<a class="headerlink" href="#context-managers" title="Permalink to this headline"></a></h3>
<p>The alure API defines many objects that need manual tear-down in
a particular order. Instead of trying to be clever and perform automatic
clean-ups at garbage collection, we should put the user in control.
To quote <em>The Zen of Python</em>,</p>
<blockquote>
<div><div class="line-block">
<div class="line">If the implementation is hard to explain, its a bad idea.</div>
<div class="line">If the implementation is easy to explain, it may be a good idea.</div>
</div>
</div></blockquote>
<p>With that being said, it does not mean we do not provide any level of
abstraction. A simplified case in point would be</p>
<div class="highlight-cython notranslate"><div class="highlight"><pre><span></span><span class="k">cdef</span> <span class="k">class</span> <span class="nf">Device</span><span class="p">:</span>
<span class="k">cdef</span> <span class="kt">alure</span>.<span class="kt">Device</span> <span class="nf">impl</span>
<span class="k">def</span> <span class="nf">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">name</span><span class="p">:</span> <span class="nb">str</span> <span class="o">=</span> <span class="s">&#39;&#39;</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="bp">None</span><span class="p">:</span>
<span class="bp">self</span><span class="o">.</span><span class="n">impl</span> <span class="o">=</span> <span class="n">devmgr</span><span class="o">.</span><span class="n">open_playback</span><span class="p">(</span><span class="n">name</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">__enter__</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">Device</span><span class="p">:</span>
<span class="k">return</span> <span class="bp">self</span>
<span class="k">def</span> <span class="nf">__exit__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="o">*</span><span class="n">exc</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">bool</span><span class="p">]:</span>
<span class="bp">self</span><span class="o">.</span><span class="n">close</span><span class="p">()</span>
<span class="k">def</span> <span class="nf">close</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="bp">None</span><span class="p">:</span>
<span class="bp">self</span><span class="o">.</span><span class="n">impl</span><span class="o">.</span><span class="n">close</span><span class="p">()</span>
</pre></div>
</div>
<p>Now if the <code class="docutils literal notranslate"><span class="pre">with</span></code> statement is used, it will make sure the device
will be closed, regardless of whatever may happen within the inner block</p>
<div class="highlight-python notranslate"><div class="highlight"><pre><span></span><span class="k">with</span> <span class="n">Device</span><span class="p">()</span> <span class="k">as</span> <span class="n">dev</span><span class="p">:</span>
<span class="o">...</span>
</pre></div>
</div>
<p>as it is equivalent to</p>
<div class="highlight-python notranslate"><div class="highlight"><pre><span></span><span class="n">dev</span> <span class="o">=</span> <span class="n">Device</span><span class="p">()</span>
<span class="k">try</span><span class="p">:</span>
<span class="o">...</span>
<span class="k">finally</span><span class="p">:</span>
<span class="n">dev</span><span class="o">.</span><span class="n">close</span><span class="p">()</span>
</pre></div>
</div>
<p>Other than closure/destruction of objects, typical uses of <a class="reference external" href="https://docs.python.org/3/reference/datamodel.html#context-managers">context managers</a>
also include saving and restoring various kinds of global state (as seen in
<a class="reference internal" href="reference.html#palace.Context" title="palace.Context"><code class="xref py py-class docutils literal notranslate"><span class="pre">palace.Context</span></code></a>), locking and unlocking resources, etc.</p>
</div>
</div>
<div class="section" id="the-double-reference">
<h2>The Double Reference<a class="headerlink" href="#the-double-reference" title="Permalink to this headline"></a></h2>
<p>While wrapping C++ interfaces, <a class="reference internal" href="#impl-idiom"><span class="std std-ref">the impl idiom</span></a> might not
be adequate, since the derived Python methods need to be callable from C++.
Luckily, Cython can handle Python objects within C++ classes just fine,
although well need to handle the reference count ourselves, e.g.</p>
<div class="highlight-cython notranslate"><div class="highlight"><pre><span></span><span class="k">cdef</span> <span class="kt">cppclass</span> <span class="nf">CppDecoder</span><span class="p">(</span><span class="n">alure</span><span class="o">.</span><span class="n">BaseDecoder</span><span class="p">):</span>
<span class="n">Decoder</span> <span class="n">pyo</span>
<span class="n">__init__</span><span class="p">(</span><span class="n">Decoder</span> <span class="n">decoder</span><span class="p">):</span>
<span class="n">this</span><span class="o">.</span><span class="n">pyo</span> <span class="o">=</span> <span class="n">decoder</span>
<span class="n">Py_INCREF</span><span class="p">(</span><span class="n">pyo</span><span class="p">)</span>
<span class="n">__dealloc__</span><span class="p">():</span>
<span class="n">Py_DECREF</span><span class="p">(</span><span class="n">pyo</span><span class="p">)</span>
<span class="nb">bool</span> <span class="n">seek</span><span class="p">(</span><span class="n">uint64_t</span> <span class="n">pos</span><span class="p">):</span>
<span class="k">return</span> <span class="n">pyo</span><span class="o">.</span><span class="n">seek</span><span class="p">(</span><span class="n">pos</span><span class="p">)</span>
</pre></div>
</div>
<p>With this being done, we can now write the wrapper as simply as</p>
<div class="highlight-cython notranslate"><div class="highlight"><pre><span></span><span class="k">cdef</span> <span class="k">class</span> <span class="nf">BaseDecoder</span><span class="p">:</span>
<span class="k">cdef</span> <span class="kt">shared_ptr</span>[<span class="kt">alure</span>.<span class="kt">Decoder</span>] <span class="nf">pimpl</span>
<span class="k">def</span> <span class="nf">__cinit__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="bp">None</span><span class="p">:</span>
<span class="bp">self</span><span class="o">.</span><span class="n">pimpl</span> <span class="o">=</span> <span class="n">shared_ptr</span><span class="p">[</span><span class="n">alure</span><span class="o">.</span><span class="n">Decoder</span><span class="p">](</span><span class="n">new</span> <span class="n">CppDecoder</span><span class="p">(</span><span class="bp">self</span><span class="p">))</span>
<span class="k">def</span> <span class="nf">seek</span><span class="p">(</span><span class="n">pos</span><span class="p">:</span> <span class="nb">int</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">bool</span><span class="p">:</span>
<span class="o">...</span>
</pre></div>
</div>
<p>Because <code class="docutils literal notranslate"><span class="pre">__cinit__</span></code> is called by <code class="docutils literal notranslate"><span class="pre">__new__</span></code>, any Python class derived
from <code class="docutils literal notranslate"><span class="pre">BaseDecoder</span></code> will be exposed to C++ as an attribute of <code class="docutils literal notranslate"><span class="pre">CppDecoder</span></code>.
Effectively, this means the users can have the alure API calling their
inherited Python object as naturally as if palace is implemented in pure Python.</p>
<p>In practice, <a class="reference internal" href="reference.html#palace.BaseDecoder" title="palace.BaseDecoder"><code class="xref py py-class docutils literal notranslate"><span class="pre">palace.BaseDecoder</span></code></a> will also need to take into account
other guarding mechanisms like <code class="xref py py-class docutils literal notranslate"><span class="pre">abc.ABC</span></code>. Due to Cython limitations,
implementation as a pure Python class and <a class="reference internal" href="#getter-setter"><span class="std std-ref">aliasing</span></a> of
<code class="docutils literal notranslate"><span class="pre">&#64;getter</span></code>/<code class="docutils literal notranslate"><span class="pre">&#64;setter</span></code> should be considered.</p>
</div>
</div>
</div>
</div>
</div>
<div class="sphinxsidebar" role="navigation" aria-label="main navigation">
<div class="sphinxsidebarwrapper">
<h1 class="logo"><a href="index.html">palace</a></h1>
<h3>Navigation</h3>
<p class="caption"><span class="caption-text">Contents:</span></p>
<ul class="current">
<li class="toctree-l1"><a class="reference internal" href="installation.html">Installation</a></li>
<li class="toctree-l1 current"><a class="current reference internal" href="#">Design Principles</a><ul>
<li class="toctree-l2"><a class="reference internal" href="#the-impl-idiom">The Impl Idiom</a></li>
<li class="toctree-l2"><a class="reference internal" href="#the-modern-python">The Modern Python</a></li>
<li class="toctree-l2"><a class="reference internal" href="#the-double-reference">The Double Reference</a></li>
</ul>
</li>
<li class="toctree-l1"><a class="reference internal" href="reference.html">Reference</a></li>
</ul>
<div class="relations">
<h3>Related Topics</h3>
<ul>
<li><a href="index.html">Documentation overview</a><ul>
<li>Previous: <a href="installation.html" title="previous chapter">Installation</a></li>
<li>Next: <a href="reference.html" title="next chapter">Reference</a></li>
</ul></li>
</ul>
</div>
<div id="searchbox" style="display: none" role="search">
<h3 id="searchlabel">Quick search</h3>
<div class="searchformwrapper">
<form class="search" action="search.html" method="get">
<input type="text" name="q" aria-labelledby="searchlabel" />
<input type="submit" value="Go" />
</form>
</div>
</div>
<script>$('#searchbox').show(0);</script>
</div>
</div>
<div class="clearer"></div>
</div>
<div class="footer">
&copy;2019, 2020 Nguyễn Gia Phong et al.
|
Powered by <a href="http://sphinx-doc.org/">Sphinx 2.4.3</a>
&amp; <a href="https://github.com/bitprophet/alabaster">Alabaster 0.7.12</a>
|
<a href="_sources/design.rst.txt"
rel="nofollow">Page source</a>
</div>
</body>
</html>

View File

@ -557,6 +557,7 @@
<p class="caption"><span class="caption-text">Contents:</span></p>
<ul>
<li class="toctree-l1"><a class="reference internal" href="installation.html">Installation</a></li>
<li class="toctree-l1"><a class="reference internal" href="design.html">Design Principles</a></li>
<li class="toctree-l1"><a class="reference internal" href="reference.html">Reference</a></li>
</ul>

View File

@ -4,7 +4,7 @@
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta charset="utf-8" />
<title>Welcome to palaces documentation! &#8212; palace 0.0.12 documentation</title>
<title>Welcome to our palace! &#8212; palace 0.0.12 documentation</title>
<link rel="stylesheet" href="_static/alabaster.css" type="text/css" />
<link rel="stylesheet" href="_static/pygments.css" type="text/css" />
<script id="documentation_options" data-url_root="./" src="_static/documentation_options.js"></script>
@ -31,8 +31,8 @@
<div class="body" role="main">
<div class="section" id="welcome-to-palace-s-documentation">
<h1>Welcome to palaces documentation!<a class="headerlink" href="#welcome-to-palace-s-documentation" title="Permalink to this headline"></a></h1>
<div class="section" id="welcome-to-our-palace">
<h1>Welcome to our palace!<a class="headerlink" href="#welcome-to-our-palace" title="Permalink to this headline"></a></h1>
<div class="toctree-wrapper compound">
<p class="caption"><span class="caption-text">Contents:</span></p>
<ul>
@ -42,9 +42,13 @@
<li class="toctree-l2"><a class="reference internal" href="installation.html#from-source">From source</a></li>
</ul>
</li>
<li class="toctree-l1"><a class="reference internal" href="reference.html">Reference</a><ul class="simple">
<li class="toctree-l1"><a class="reference internal" href="design.html">Design Principles</a><ul>
<li class="toctree-l2"><a class="reference internal" href="design.html#the-impl-idiom">The Impl Idiom</a></li>
<li class="toctree-l2"><a class="reference internal" href="design.html#the-modern-python">The Modern Python</a></li>
<li class="toctree-l2"><a class="reference internal" href="design.html#the-double-reference">The Double Reference</a></li>
</ul>
</li>
<li class="toctree-l1"><a class="reference internal" href="reference.html">Reference</a></li>
</ul>
</div>
</div>
@ -77,6 +81,7 @@
<p class="caption"><span class="caption-text">Contents:</span></p>
<ul>
<li class="toctree-l1"><a class="reference internal" href="installation.html">Installation</a></li>
<li class="toctree-l1"><a class="reference internal" href="design.html">Design Principles</a></li>
<li class="toctree-l1"><a class="reference internal" href="reference.html">Reference</a></li>
</ul>

Binary file not shown.

View File

@ -75,6 +75,7 @@
<p class="caption"><span class="caption-text">Contents:</span></p>
<ul>
<li class="toctree-l1"><a class="reference internal" href="installation.html">Installation</a></li>
<li class="toctree-l1"><a class="reference internal" href="design.html">Design Principles</a></li>
<li class="toctree-l1"><a class="reference internal" href="reference.html">Reference</a></li>
</ul>

View File

@ -14,7 +14,7 @@
<script src="_static/language_data.js"></script>
<link rel="index" title="Index" href="genindex.html" />
<link rel="search" title="Search" href="search.html" />
<link rel="prev" title="Installation" href="installation.html" />
<link rel="prev" title="Design Principles" href="design.html" />
<link rel="stylesheet" href="_static/custom.css" type="text/css" />
@ -31,11 +31,9 @@
<div class="body" role="main">
<div class="section" id="reference">
<h1>Reference<a class="headerlink" href="#reference" title="Permalink to this headline"></a></h1>
<div class="toctree-wrapper compound">
</div>
<span class="target" id="module-palace"></span><p>Pythonic Audio Library and Codecs Environment</p>
<div class="section" id="module-palace">
<span id="reference"></span><h1>Reference<a class="headerlink" href="#module-palace" title="Permalink to this headline"></a></h1>
<p>Pythonic Audio Library and Codecs Environment</p>
<dl class="attribute">
<dt id="palace.Vector3">
<code class="sig-prename descclassname">palace.</code><code class="sig-name descname">Vector3</code><a class="headerlink" href="#palace.Vector3" title="Permalink to this definition"></a></dt>
@ -1637,16 +1635,15 @@ or stream, which is detected upon a call to <cite>Context.update</cite>.</p>
<p class="caption"><span class="caption-text">Contents:</span></p>
<ul class="current">
<li class="toctree-l1"><a class="reference internal" href="installation.html">Installation</a></li>
<li class="toctree-l1 current"><a class="current reference internal" href="#">Reference</a><ul class="simple">
</ul>
</li>
<li class="toctree-l1"><a class="reference internal" href="design.html">Design Principles</a></li>
<li class="toctree-l1 current"><a class="current reference internal" href="#">Reference</a></li>
</ul>
<div class="relations">
<h3>Related Topics</h3>
<ul>
<li><a href="index.html">Documentation overview</a><ul>
<li>Previous: <a href="installation.html" title="previous chapter">Installation</a></li>
<li>Previous: <a href="design.html" title="previous chapter">Design Principles</a></li>
</ul></li>
</ul>
</div>

View File

@ -78,6 +78,7 @@
<p class="caption"><span class="caption-text">Contents:</span></p>
<ul>
<li class="toctree-l1"><a class="reference internal" href="installation.html">Installation</a></li>
<li class="toctree-l1"><a class="reference internal" href="design.html">Design Principles</a></li>
<li class="toctree-l1"><a class="reference internal" href="reference.html">Reference</a></li>
</ul>

File diff suppressed because one or more lines are too long

185
src/design.rst Normal file
View File

@ -0,0 +1,185 @@
Design Principles
=================
In this section, we will discuss a few design principles in order to write
a safe, efficient, easy-to-use and extendable 3D audio library for Python,
by wrapping existing functionalities from the C++ API alure_.
This part of the documentation assumes its reader are at least familiar with
Cython, Python and C++11.
.. _alure: https://github.com/kcat/alure
.. _impl-idiom:
The Impl Idiom
--------------
*Not to be confused with* `the pimpl idiom`_.
For memory-safety, whenever possible, we rely on Cython for allocation and
deallocation of C++ objects. To do this, the nullary constructor needs to be
(re-)declared in Cython, e.g.
.. code-block:: cython
cdef extern from 'foobar.h' namespace 'foobar':
cdef cppclass Foo:
Foo()
float meth(size_t crack) except +
...
The Cython extension type can then be declared as follows
.. code-block:: cython
cdef class Bar:
cdef Foo impl
def __init__(self, *args, **kwargs):
self.impl = ...
@staticmethod
def from_baz(baz: Baz) -> Bar:
bar = Bar.__new__(Bar)
bar.impl = ...
return bar
def meth(self, crack: int) -> float:
return self.impl.meth(crack)
.. _`the pimpl idiom`: https://wiki.c2.com/?PimplIdiom
The Modern Python
-----------------
One of the goal of palace is to create a Pythonic, i.e. intuitive and concise,
interface. To achieve this, we try to make use of some modern Python features,
which not only allow users to adopt palace with ease, but also make their
programs more readable and less error-prone.
.. _getter-setter:
Property Attributes
^^^^^^^^^^^^^^^^^^^
A large proportion of alure API are getters/setter methods. In Python,
it is a good practice to use property_ to abstract these calls, and thus make
the interface more natural with attribute-like referencing and assignments.
Due to implementation details, Cython has to hijack the ``@property`` decorator
to make it work for read-write properties. Unfortunately, the Cython-generated
descriptors do not play very well with other builtin decorators, thus in some
cases, it is recommended to alias the call to ``property`` as follows
.. code-block:: python
getter = property
setter = lambda fset: property(fset=fset, doc=fset.__doc__)
Then ``@getter`` and ``@setter`` can be used to decorate read-only and
write-only properties, respectively, without any trouble even if other
decorators are used for the same extension type method.
.. _property: https://docs.python.org/3/library/functions.html#property
Context Managers
^^^^^^^^^^^^^^^^
The alure API defines many objects that need manual tear-down in
a particular order. Instead of trying to be clever and perform automatic
clean-ups at garbage collection, we should put the user in control.
To quote *The Zen of Python*,
| If the implementation is hard to explain, it's a bad idea.
| If the implementation is easy to explain, it may be a good idea.
With that being said, it does not mean we do not provide any level of
abstraction. A simplified case in point would be
.. code-block:: cython
cdef class Device:
cdef alure.Device impl
def __init__(self, name: str = '') -> None:
self.impl = devmgr.open_playback(name)
def __enter__(self) -> Device:
return self
def __exit__(self, *exc) -> Optional[bool]:
self.close()
def close(self) -> None:
self.impl.close()
Now if the ``with`` statement is used, it will make sure the device
will be closed, regardless of whatever may happen within the inner block
.. code-block:: python
with Device() as dev:
...
as it is equivalent to
.. code-block:: python
dev = Device()
try:
...
finally:
dev.close()
Other than closure/destruction of objects, typical uses of `context managers`__
also include saving and restoring various kinds of global state (as seen in
:py:class:`palace.Context`), locking and unlocking resources, etc.
__ https://docs.python.org/3/reference/datamodel.html#context-managers
The Double Reference
--------------------
While wrapping C++ interfaces, :ref:`the impl idiom <impl-idiom>` might not
be adequate, since the derived Python methods need to be callable from C++.
Luckily, Cython can handle Python objects within C++ classes just fine,
although we'll need to handle the reference count ourselves, e.g.
.. code-block:: cython
cdef cppclass CppDecoder(alure.BaseDecoder):
Decoder pyo
__init__(Decoder decoder):
this.pyo = decoder
Py_INCREF(pyo)
__dealloc__():
Py_DECREF(pyo)
bool seek(uint64_t pos):
return pyo.seek(pos)
With this being done, we can now write the wrapper as simply as
.. code-block:: cython
cdef class BaseDecoder:
cdef shared_ptr[alure.Decoder] pimpl
def __cinit__(self, *args, **kwargs) -> None:
self.pimpl = shared_ptr[alure.Decoder](new CppDecoder(self))
def seek(pos: int) -> bool:
...
Because ``__cinit__`` is called by ``__new__``, any Python class derived
from ``BaseDecoder`` will be exposed to C++ as an attribute of ``CppDecoder``.
Effectively, this means the users can have the alure API calling their
inherited Python object as naturally as if palace is implemented in pure Python.
In practice, :py:class:`palace.BaseDecoder` will also need to take into account
other guarding mechanisms like :py:class:`abc.ABC`. Due to Cython limitations,
implementation as a pure Python class and :ref:`aliasing <getter-setter>` of
``@getter``/``@setter`` should be considered.

View File

@ -1,16 +1,12 @@
.. palace documentation master file, created by
sphinx-quickstart on Sun Jan 5 19:56:04 2020.
You can adapt this file completely to your liking, but it should at least
contain the root `toctree` directive.
Welcome to palace's documentation!
==================================
Welcome to our palace!
======================
.. toctree::
:maxdepth: 2
:caption: Contents:
installation
design
reference

View File

@ -1,7 +1,5 @@
Reference
=========
.. toctree::
.. automodule:: palace
:members: