palace/design.html

383 lines
22 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html class="writer-html5" lang="en" >
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Design Principles &mdash; palace 0.2.1 documentation</title>
<link rel="stylesheet" href="_static/css/theme.css" type="text/css" />
<link rel="stylesheet" href="_static/pygments.css" type="text/css" />
<!--[if lt IE 9]>
<script src="_static/js/html5shiv.min.js"></script>
<![endif]-->
<script type="text/javascript" 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>
<script type="text/javascript" src="_static/js/theme.js"></script>
<link rel="index" title="Index" href="genindex.html" />
<link rel="search" title="Search" href="search.html" />
<link rel="next" title="Getting Involved" href="contributing.html" />
<link rel="prev" title="File I/O Interface" href="reference/file-io.html" />
</head>
<body class="wy-body-for-nav">
<div class="wy-grid-for-nav">
<nav data-toggle="wy-nav-shift" class="wy-nav-side">
<div class="wy-side-scroll">
<div class="wy-side-nav-search" >
<a href="index.html" class="icon icon-home" alt="Documentation Home"> palace
</a>
<div role="search">
<form id="rtd-search-form" class="wy-form" action="search.html" method="get">
<input type="text" name="q" placeholder="Search docs" />
<input type="hidden" name="check_keywords" value="yes" />
<input type="hidden" name="area" value="default" />
</form>
</div>
</div>
<div class="wy-menu wy-menu-vertical" data-spy="affix" role="navigation" aria-label="main navigation">
<p class="caption"><span class="caption-text">Table of Contents</span></p>
<ul class="current">
<li class="toctree-l1"><a class="reference internal" href="installation.html">Installation</a></li>
<li class="toctree-l1"><a class="reference internal" href="tutorial/index.html">Tutorial</a></li>
<li class="toctree-l1"><a class="reference internal" href="reference/index.html">Reference</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><ul>
<li class="toctree-l3"><a class="reference internal" href="#property-attributes">Property Attributes</a></li>
<li class="toctree-l3"><a class="reference internal" href="#context-managers">Context Managers</a></li>
</ul>
</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="contributing.html">Getting Involved</a></li>
<li class="toctree-l1"><a class="reference internal" href="copying.html">Copying</a></li>
</ul>
<p class="caption"><span class="caption-text">Quick Navigation</span></p>
<ul>
<li class="toctree-l1"><a class="reference external" href="https://pypi.org/project/palace/">Python Package Index</a></li>
<li class="toctree-l1"><a class="reference external" href="https://travis-ci.com/github/McSinyx/palace">Travis CI Build</a></li>
<li class="toctree-l1"><a class="reference external" href="https://ci.appveyor.com/project/McSinyx/palace">AppVeyor Build</a></li>
<li class="toctree-l1"><a class="reference external" href="https://github.com/McSinyx/palace">GitHub Repository</a></li>
<li class="toctree-l1"><a class="reference external" href="https://matrix.to/#/#palace-dev:matrix.org">Matrix Chat Room</a></li>
</ul>
</div>
</div>
</nav>
<section data-toggle="wy-nav-shift" class="wy-nav-content-wrap">
<nav class="wy-nav-top" aria-label="top navigation">
<i data-toggle="wy-nav-top" class="fa fa-bars"></i>
<a href="index.html">palace</a>
</nav>
<div class="wy-nav-content">
<div class="rst-content">
<div role="navigation" aria-label="breadcrumbs navigation">
<ul class="wy-breadcrumbs">
<li><a href="index.html" class="icon icon-home"></a> &raquo;</li>
<li>Design Principles</li>
<li class="wy-breadcrumbs-aside">
<a href="_sources/design.rst.txt" rel="nofollow"> View page source</a>
</li>
</ul>
<hr/>
</div>
<div role="main" class="document" itemscope="itemscope" itemtype="http://schema.org/Article">
<div itemprop="articleBody">
<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/context.html#palace.Context" title="palace.Context"><code class="xref py py-class docutils literal notranslate"><span class="pre">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/decoder.html#palace.BaseDecoder" title="palace.BaseDecoder"><code class="xref py py-class docutils literal notranslate"><span class="pre">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>
<footer>
<div class="rst-footer-buttons" role="navigation" aria-label="footer navigation">
<a href="contributing.html" class="btn btn-neutral float-right" title="Getting Involved" accesskey="n" rel="next">Next <span class="fa fa-arrow-circle-right"></span></a>
<a href="reference/file-io.html" class="btn btn-neutral float-left" title="File I/O Interface" accesskey="p" rel="prev"><span class="fa fa-arrow-circle-left"></span> Previous</a>
</div>
<hr/>
<div role="contentinfo">
<p>
&copy; Copyright 2019, 2020 Nguyễn Gia Phong et al
</p>
</div>
Built with <a href="http://sphinx-doc.org/">Sphinx</a> using a
<a href="https://github.com/rtfd/sphinx_rtd_theme">theme</a>
provided by <a href="https://readthedocs.org">Read the Docs</a>.
</footer>
</div>
</div>
</section>
</div>
<script type="text/javascript">
jQuery(function () {
SphinxRtdTheme.Navigation.enable(true);
});
</script>
</body>
</html>