mirror of
https://github.com/evennia/evennia.git
synced 2026-03-17 13:26:30 +01:00
361 lines
No EOL
27 KiB
HTML
361 lines
No EOL
27 KiB
HTML
<!DOCTYPE html>
|
||
|
||
<html lang="en" data-content_root="../">
|
||
<head>
|
||
<meta charset="utf-8" />
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0" /><meta name="viewport" content="width=device-width, initial-scale=1" />
|
||
|
||
<title>Profiling — Evennia latest documentation</title>
|
||
<link rel="stylesheet" type="text/css" href="../_static/pygments.css?v=d75fae25" />
|
||
<link rel="stylesheet" type="text/css" href="../_static/nature.css?v=279e0f84" />
|
||
<link rel="stylesheet" type="text/css" href="../_static/custom.css?v=e4a91a55" />
|
||
<script src="../_static/documentation_options.js?v=c6e86fd7"></script>
|
||
<script src="../_static/doctools.js?v=9bcbadda"></script>
|
||
<script src="../_static/sphinx_highlight.js?v=dc90522c"></script>
|
||
<link rel="icon" href="../_static/favicon.ico"/>
|
||
<link rel="index" title="Index" href="../genindex.html" />
|
||
<link rel="search" title="Search" href="../search.html" />
|
||
<link rel="next" title="Continuous Integration (CI)" href="Continuous-Integration.html" />
|
||
<link rel="prev" title="Unit Testing" href="Unit-Testing.html" />
|
||
</head><body>
|
||
<div class="related" role="navigation" aria-label="Related">
|
||
<h3>Navigation</h3>
|
||
<ul>
|
||
<li class="right" style="margin-right: 10px">
|
||
<a href="../genindex.html" title="General Index"
|
||
accesskey="I">index</a></li>
|
||
<li class="right" >
|
||
<a href="../py-modindex.html" title="Python Module Index"
|
||
>modules</a> |</li>
|
||
<li class="right" >
|
||
<a href="Continuous-Integration.html" title="Continuous Integration (CI)"
|
||
accesskey="N">next</a> |</li>
|
||
<li class="right" >
|
||
<a href="Unit-Testing.html" title="Unit Testing"
|
||
accesskey="P">previous</a> |</li>
|
||
<li class="nav-item nav-item-0"><a href="../index.html">Evennia</a> »</li>
|
||
<li class="nav-item nav-item-1"><a href="Coding-Overview.html" accesskey="U">Coding and development help</a> »</li>
|
||
<li class="nav-item nav-item-this"><a href="">Profiling</a></li>
|
||
</ul>
|
||
</div>
|
||
|
||
<div class="document">
|
||
<div class="documentwrapper">
|
||
<div class="bodywrapper">
|
||
<div class="body" role="main">
|
||
|
||
<section class="tex2jax_ignore mathjax_ignore" id="profiling">
|
||
<h1>Profiling<a class="headerlink" href="#profiling" title="Link to this heading">¶</a></h1>
|
||
<div class="admonition important">
|
||
<p class="admonition-title">Important</p>
|
||
<p>This is considered an advanced topic. It’s mainly of interest to server developers.</p>
|
||
</div>
|
||
<p>Sometimes it can be useful to try to determine just how efficient a particular piece of code is, or to figure out if one could speed up things more than they are. There are many ways to test the performance of Python and the running server.</p>
|
||
<p>Before digging into this section, remember Donald Knuth’s <a class="reference external" href="https://en.wikipedia.org/wiki/Program_optimization#When_to_optimize">words of wisdom</a>:</p>
|
||
<blockquote>
|
||
<div><p><em>[…]about 97% of the time: Premature optimization is the root of all evil</em>.</p>
|
||
</div></blockquote>
|
||
<p>That is, don’t start to try to optimize your code until you have actually identified a need to do so. This means your code must actually be working before you start to consider optimization. Optimization will also often make your code more complex and harder to read. Consider readability and maintainability and you may find that a small gain in speed is just not worth it.</p>
|
||
<section id="simple-timer-tests">
|
||
<h2>Simple timer tests<a class="headerlink" href="#simple-timer-tests" title="Link to this heading">¶</a></h2>
|
||
<p>Python’s <code class="docutils literal notranslate"><span class="pre">timeit</span></code> module is very good for testing small things. For example, in
|
||
order to test if it is faster to use a <code class="docutils literal notranslate"><span class="pre">for</span></code> loop or a list comprehension you
|
||
could use the following code:</p>
|
||
<div class="highlight-python notranslate"><div class="highlight"><pre><span></span> <span class="kn">import</span><span class="w"> </span><span class="nn">timeit</span>
|
||
<span class="c1"># Time to do 1000000 for loops</span>
|
||
<span class="n">timeit</span><span class="o">.</span><span class="n">timeit</span><span class="p">(</span><span class="s2">"for i in range(100):</span><span class="se">\n</span><span class="s2"> a.append(i)"</span><span class="p">,</span> <span class="n">setup</span><span class="o">=</span><span class="s2">"a = []"</span><span class="p">)</span>
|
||
<span class="o"><<<</span> <span class="mf">10.70982813835144</span>
|
||
<span class="c1"># Time to do 1000000 list comprehensions</span>
|
||
<span class="n">timeit</span><span class="o">.</span><span class="n">timeit</span><span class="p">(</span><span class="s2">"a = [i for i in range(100)]"</span><span class="p">)</span>
|
||
<span class="o"><<<</span> <span class="mf">5.358283996582031</span>
|
||
</pre></div>
|
||
</div>
|
||
<p>The <code class="docutils literal notranslate"><span class="pre">setup</span></code> keyword is used to set up things that should not be included in the time measurement, like <code class="docutils literal notranslate"><span class="pre">a</span> <span class="pre">=</span> <span class="pre">[]</span></code> in the first call.</p>
|
||
<p>By default the <code class="docutils literal notranslate"><span class="pre">timeit</span></code> function will re-run the given test 1000000 times and returns the <em>total time</em> to do so (so <em>not</em> the average per test). A hint is to not use this default for testing something that includes database writes - for that you may want to use a lower number of repeats (say 100 or 1000) using the <code class="docutils literal notranslate"><span class="pre">number=100</span></code> keyword.</p>
|
||
<p>In the example above, we see that this number of calls, using a list comprehension is about twice as fast as building a list using <code class="docutils literal notranslate"><span class="pre">.append()</span></code>.</p>
|
||
</section>
|
||
<section id="using-cprofile">
|
||
<h2>Using cProfile<a class="headerlink" href="#using-cprofile" title="Link to this heading">¶</a></h2>
|
||
<p>Python comes with its own profiler, named cProfile (this is for cPython, no tests have been done with <code class="docutils literal notranslate"><span class="pre">pypy</span></code> at this point). Due to the way Evennia’s processes are handled, there is no point in using the normal way to start the profiler (<code class="docutils literal notranslate"><span class="pre">python</span> <span class="pre">-m</span> <span class="pre">cProfile</span> <span class="pre">evennia.py</span></code>). Instead you start the profiler through the launcher:</p>
|
||
<div class="highlight-none notranslate"><div class="highlight"><pre><span></span>evennia --profiler start
|
||
</pre></div>
|
||
</div>
|
||
<p>This will start Evennia with the Server component running (in daemon mode) under cProfile. You could instead try <code class="docutils literal notranslate"><span class="pre">--profile</span></code> with the <code class="docutils literal notranslate"><span class="pre">portal</span></code> argument to profile the Portal (you would then need to <a class="reference internal" href="../Setup/Running-Evennia.html"><span class="std std-doc">start the Server separately</span></a>).</p>
|
||
<p>Please note that while the profiler is running, your process will use a lot more memory than usual. Memory usage is even likely to climb over time. So don’t leave it running perpetually but monitor it carefully (for example using the <code class="docutils literal notranslate"><span class="pre">top</span></code> command on Linux or the Task Manager’s memory display on Windows).</p>
|
||
<p>Once you have run the server for a while, you need to stop it so the profiler can give its report. Do <em>not</em> kill the program from your task manager or by sending it a kill signal - this will most likely also mess with the profiler. Instead either use <code class="docutils literal notranslate"><span class="pre">evennia.py</span> <span class="pre">stop</span></code> or (which may be even better), use <code class="docutils literal notranslate"><span class="pre">@shutdown</span></code> from inside the game.</p>
|
||
<p>Once the server has fully shut down (this may be a lot slower than usual) you will find that profiler has created a new file <code class="docutils literal notranslate"><span class="pre">mygame/server/logs/server.prof</span></code>.</p>
|
||
<section id="analyzing-the-profile">
|
||
<h3>Analyzing the profile<a class="headerlink" href="#analyzing-the-profile" title="Link to this heading">¶</a></h3>
|
||
<p>The <code class="docutils literal notranslate"><span class="pre">server.prof</span></code> file is a binary file. There are many ways to analyze and display its contents, all of which has only been tested in Linux (If you are a Windows/Mac user, let us know what works).</p>
|
||
<p>You can look at the contents of the profile file with Python’s in-built <code class="docutils literal notranslate"><span class="pre">pstats</span></code> module in the evennia shell (it’s recommended you install <code class="docutils literal notranslate"><span class="pre">ipython</span></code> with <code class="docutils literal notranslate"><span class="pre">pip</span> <span class="pre">install</span> <span class="pre">ipython</span></code> in your virtualenv first, for prettier output):</p>
|
||
<div class="highlight-none notranslate"><div class="highlight"><pre><span></span>evennia shell
|
||
</pre></div>
|
||
</div>
|
||
<p>Then in the shell</p>
|
||
<div class="highlight-python notranslate"><div class="highlight"><pre><span></span><span class="kn">import</span><span class="w"> </span><span class="nn">pstats</span>
|
||
<span class="kn">from</span><span class="w"> </span><span class="nn">pstats</span><span class="w"> </span><span class="kn">import</span> <span class="n">SortKey</span>
|
||
|
||
<span class="n">p</span> <span class="o">=</span> <span class="n">pstats</span><span class="o">.</span><span class="n">Stats</span><span class="p">(</span><span class="s1">'server/log/server.prof'</span><span class="p">)</span>
|
||
<span class="n">p</span><span class="o">.</span><span class="n">strip_dirs</span><span class="p">()</span><span class="o">.</span><span class="n">sort_stats</span><span class="p">(</span><span class="o">-</span><span class="mi">1</span><span class="p">)</span><span class="o">.</span><span class="n">print_stats</span><span class="p">()</span>
|
||
|
||
</pre></div>
|
||
</div>
|
||
<p>See the <a class="reference external" href="https://docs.python.org/3/library/profile.html#instant-user-s-manual">Python profiling documentation</a> for more information.</p>
|
||
<p>You can also visualize the data in various ways.</p>
|
||
<ul class="simple">
|
||
<li><p><a class="reference external" href="https://pypi.org/project/RunSnakeRun/">Runsnake</a> visualizes the profile to
|
||
give a good overview. Install with <code class="docutils literal notranslate"><span class="pre">pip</span> <span class="pre">install</span> <span class="pre">runsnakerun</span></code>. Note that this
|
||
may require a C compiler and be quite slow to install.</p></li>
|
||
<li><p>For more detailed listing of usage time, you can use
|
||
<a class="reference external" href="http://kcachegrind.sourceforge.net/html/Home.html">KCachegrind</a>. To make
|
||
KCachegrind work with Python profiles you also need the wrapper script
|
||
<a class="reference external" href="https://pypi.python.org/pypi/pyprof2calltree/">pyprof2calltree</a>. You can get
|
||
<code class="docutils literal notranslate"><span class="pre">pyprof2calltree</span></code> via <code class="docutils literal notranslate"><span class="pre">pip</span></code> whereas KCacheGrind is something you need to get
|
||
via your package manager or their homepage.</p></li>
|
||
</ul>
|
||
<p>How to analyze and interpret profiling data is not a trivial issue and depends on what you are profiling for. Evennia being an asynchronous server can also confuse profiling. Ask on the mailing list if you need help and be ready to be able to supply your <code class="docutils literal notranslate"><span class="pre">server.prof</span></code> file for comparison, along with the exact conditions under which it was obtained.</p>
|
||
</section>
|
||
</section>
|
||
<section id="the-dummyrunner">
|
||
<h2>The Dummyrunner<a class="headerlink" href="#the-dummyrunner" title="Link to this heading">¶</a></h2>
|
||
<p>It is difficult to test “actual” game performance without having players in your game. For this reason Evennia comes with the <em>Dummyrunner</em> system. The Dummyrunner is a stress-testing system: a separate program that logs into your game with simulated players (aka “bots” or “dummies”). Once connected, these dummies will semi-randomly perform various tasks from a list of possible actions. Use <code class="docutils literal notranslate"><span class="pre">Ctrl-C</span></code> to stop the Dummyrunner.</p>
|
||
<div class="admonition warning">
|
||
<p class="admonition-title">Warning</p>
|
||
<div class="highlight-none notranslate"><div class="highlight"><pre><span></span>You should not run the Dummyrunner on a production database. It
|
||
will spawn many objects and also needs to run with general permissions.
|
||
</pre></div>
|
||
</div>
|
||
<p>This is the recommended process for using the dummy runner:</p>
|
||
</div>
|
||
<ol class="arabic">
|
||
<li><p>Stop your server completely with <code class="docutils literal notranslate"><span class="pre">evennia</span> <span class="pre">stop</span></code>.</p></li>
|
||
<li><p>At <em>the end</em> of your <code class="docutils literal notranslate"><span class="pre">mygame/server/conf.settings.py</span></code> file, add the line</p>
|
||
<div class="highlight-none notranslate"><div class="highlight"><pre><span></span> from evennia.server.profiling.settings_mixin import *
|
||
</pre></div>
|
||
</div>
|
||
<p>This will override your settings and disable Evennia’s rate limiters and DoS-protections, which would otherwise block mass-connecting clients from one IP. Notably, it will also change to a different (faster) password hasher.</p>
|
||
</li>
|
||
<li><p>(recommended): Build a new database. If you use default Sqlite3 and want to
|
||
keep your existing database, just rename <code class="docutils literal notranslate"><span class="pre">mygame/server/evennia.db3</span></code> to
|
||
<code class="docutils literal notranslate"><span class="pre">mygame/server/evennia.db3_backup</span></code> and run <code class="docutils literal notranslate"><span class="pre">evennia</span> <span class="pre">migrate</span></code> and <code class="docutils literal notranslate"><span class="pre">evennia</span> <span class="pre">start</span></code> to create a new superuser as usual.</p></li>
|
||
<li><p>(recommended) Log into the game as your superuser. This is just so you
|
||
can manually check response. If you kept an old database, you will <em>not</em>
|
||
be able to connect with an <em>existing</em> user since the password hasher changed!</p></li>
|
||
<li><p>Start the dummyrunner with 10 dummy users from the terminal with</p>
|
||
<div class="highlight-none notranslate"><div class="highlight"><pre><span></span> evennia --dummyrunner 10
|
||
</pre></div>
|
||
</div>
|
||
<p>Use <code class="docutils literal notranslate"><span class="pre">Ctrl-C</span></code> (or <code class="docutils literal notranslate"><span class="pre">Cmd-C</span></code>) to stop it.</p>
|
||
</li>
|
||
</ol>
|
||
<p>If you want to see what the dummies are actually doing you can run with a single dummy:</p>
|
||
<div class="highlight-none notranslate"><div class="highlight"><pre><span></span>evennia --dummyrunner 1
|
||
</pre></div>
|
||
</div>
|
||
<p>The inputs/outputs from the dummy will then be printed. By default the runner uses the ‘looker’ profile, which just logs in and sends the ‘look’ command over and over. To change the settings, copy the file <code class="docutils literal notranslate"><span class="pre">evennia/server/profiling/dummyrunner_settings.py</span></code> to your <code class="docutils literal notranslate"><span class="pre">mygame/server/conf/</span></code> directory, then add this line to your settings file to use it in the new location:</p>
|
||
<div class="highlight-none notranslate"><div class="highlight"><pre><span></span>DUMMYRUNNER_SETTINGS_MODULE = "server/conf/dummyrunner_settings.py"
|
||
</pre></div>
|
||
</div>
|
||
<p>The dummyrunner settings file is a python code module in its own right - it defines the actions available to the dummies. These are just tuples of command strings (like “look here”) for the dummy to send to the server along with a probability of them happening. The dummyrunner looks for a global variable <code class="docutils literal notranslate"><span class="pre">ACTIONS</span></code>, a list of tuples, where the first two elements define the commands for logging in/out of the server.</p>
|
||
<p>Below is a simplified minimal setup (the default settings file adds a lot more functionality and info):</p>
|
||
<div class="highlight-python notranslate"><div class="highlight"><pre><span></span><span class="c1"># minimal dummyrunner setup file</span>
|
||
|
||
<span class="c1"># Time between each dummyrunner "tick", in seconds. Each dummy will be called</span>
|
||
<span class="c1"># with this frequency.</span>
|
||
<span class="n">TIMESTEP</span> <span class="o">=</span> <span class="mi">1</span>
|
||
|
||
<span class="c1"># Chance of a dummy actually performing an action on a given tick. This</span>
|
||
<span class="c1"># spreads out usage randomly, like it would be in reality.</span>
|
||
<span class="n">CHANCE_OF_ACTION</span> <span class="o">=</span> <span class="mf">0.5</span>
|
||
|
||
<span class="c1"># Chance of a currently unlogged-in dummy performing its login action every</span>
|
||
<span class="c1"># tick. This emulates not all accounts logging in at exactly the same time.</span>
|
||
<span class="n">CHANCE_OF_LOGIN</span> <span class="o">=</span> <span class="mf">0.01</span>
|
||
|
||
<span class="c1"># Which telnet port to connect to. If set to None, uses the first default</span>
|
||
<span class="c1"># telnet port of the running server.</span>
|
||
<span class="n">TELNET_PORT</span> <span class="o">=</span> <span class="kc">None</span>
|
||
|
||
<span class="c1"># actions</span>
|
||
|
||
<span class="k">def</span><span class="w"> </span><span class="nf">c_login</span><span class="p">(</span><span class="n">client</span><span class="p">):</span>
|
||
<span class="n">name</span> <span class="o">=</span> <span class="sa">f</span><span class="s2">"Character-</span><span class="si">{</span><span class="n">client</span><span class="o">.</span><span class="n">gid</span><span class="si">}</span><span class="s2">"</span>
|
||
<span class="n">pwd</span> <span class="o">=</span> <span class="sa">f</span><span class="s2">"23fwsf23sdfw23wef23"</span>
|
||
<span class="k">return</span> <span class="p">(</span>
|
||
<span class="sa">f</span><span class="s2">"create </span><span class="si">{</span><span class="n">name</span><span class="si">}</span><span class="s2"> </span><span class="si">{</span><span class="n">pwd</span><span class="si">}</span><span class="s2">"</span>
|
||
<span class="sa">f</span><span class="s2">"connect </span><span class="si">{</span><span class="n">name</span><span class="si">}</span><span class="s2"> </span><span class="si">{</span><span class="n">pwd</span><span class="si">}</span><span class="s2">"</span>
|
||
<span class="p">)</span>
|
||
|
||
<span class="k">def</span><span class="w"> </span><span class="nf">c_logout</span><span class="p">(</span><span class="n">client</span><span class="p">):</span>
|
||
<span class="k">return</span> <span class="p">(</span><span class="s2">"quit"</span><span class="p">,</span> <span class="p">)</span>
|
||
|
||
<span class="k">def</span><span class="w"> </span><span class="nf">c_look</span><span class="p">(</span><span class="n">client</span><span class="p">):</span>
|
||
<span class="k">return</span> <span class="p">(</span><span class="s2">"look here"</span><span class="p">,</span> <span class="s2">"look me"</span><span class="p">)</span>
|
||
|
||
<span class="c1"># this is read by dummyrunner.</span>
|
||
<span class="n">ACTIONS</span> <span class="o">=</span> <span class="p">(</span>
|
||
<span class="n">c_login</span><span class="p">,</span>
|
||
<span class="n">c_logout</span><span class="p">,</span>
|
||
<span class="p">(</span><span class="mf">1.0</span><span class="p">,</span> <span class="n">c_look</span><span class="p">)</span> <span class="c1"># (probability, command-generator)</span>
|
||
<span class="p">)</span>
|
||
|
||
</pre></div>
|
||
</div>
|
||
<p>At the bottom of the default file are a few default profiles you can test out by just setting the <code class="docutils literal notranslate"><span class="pre">PROFILE</span></code> variable to one of the options.</p>
|
||
<section id="dummyrunner-hints">
|
||
<h3>Dummyrunner hints<a class="headerlink" href="#dummyrunner-hints" title="Link to this heading">¶</a></h3>
|
||
<ul class="simple">
|
||
<li><p>Don’t start with too many dummies. The Dummyrunner taxes the server much more
|
||
than ‘real’ users tend to do. Start with 10-100 to begin with.</p></li>
|
||
<li><p>Stress-testing can be fun, but also consider what a ‘realistic’ number of
|
||
users would be for your game.</p></li>
|
||
<li><p>Note in the dummyrunner output how many commands/s are being sent to the
|
||
server by all dummies. This is usually a lot higher than what you’d
|
||
realistically expect to see from the same number of users.</p></li>
|
||
<li><p>The default settings sets up a ‘lag’ measure to measaure the round-about
|
||
message time. It updates with an average every 30 seconds. It can be worth to
|
||
have this running for a small number of dummies in one terminal before adding
|
||
more by starting another dummyrunner in another terminal - the first one will
|
||
act as a measure of how lag changes with different loads. Also verify the
|
||
lag-times by entering commands manually in-game.</p></li>
|
||
<li><p>Check the CPU usage of your server using <code class="docutils literal notranslate"><span class="pre">top/htop</span></code> (linux). In-game, use the
|
||
<code class="docutils literal notranslate"><span class="pre">server</span></code> command.</p></li>
|
||
<li><p>You can run the server with <code class="docutils literal notranslate"><span class="pre">--profiler</span> <span class="pre">start</span></code> to test it with dummies. Note
|
||
that the profiler will itself affect server performance, especially memory
|
||
consumption.</p></li>
|
||
<li><p>Generally, the dummyrunner system makes for a decent test of general
|
||
performance; but it is of course hard to actually mimic human user behavior.
|
||
For this, actual real-game testing is required.</p></li>
|
||
</ul>
|
||
</section>
|
||
</section>
|
||
</section>
|
||
|
||
|
||
<div class="clearer"></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="sphinxsidebar" role="navigation" aria-label="Main">
|
||
<div class="sphinxsidebarwrapper">
|
||
<p class="logo"><a href="../index.html">
|
||
<img class="logo" src="../_static/evennia_logo.png" alt="Logo of Evennia"/>
|
||
</a></p>
|
||
<search 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" autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false"/>
|
||
<input type="submit" value="Go" />
|
||
</form>
|
||
</div>
|
||
</search>
|
||
<script>document.getElementById('searchbox').style.display = "block"</script>
|
||
<h3><a href="../index.html">Table of Contents</a></h3>
|
||
<ul>
|
||
<li><a class="reference internal" href="#">Profiling</a><ul>
|
||
<li><a class="reference internal" href="#simple-timer-tests">Simple timer tests</a></li>
|
||
<li><a class="reference internal" href="#using-cprofile">Using cProfile</a><ul>
|
||
<li><a class="reference internal" href="#analyzing-the-profile">Analyzing the profile</a></li>
|
||
</ul>
|
||
</li>
|
||
<li><a class="reference internal" href="#the-dummyrunner">The Dummyrunner</a><ul>
|
||
<li><a class="reference internal" href="#dummyrunner-hints">Dummyrunner hints</a></li>
|
||
</ul>
|
||
</li>
|
||
</ul>
|
||
</li>
|
||
</ul>
|
||
|
||
<div>
|
||
<h4>Previous topic</h4>
|
||
<p class="topless"><a href="Unit-Testing.html"
|
||
title="previous chapter">Unit Testing</a></p>
|
||
</div>
|
||
<div>
|
||
<h4>Next topic</h4>
|
||
<p class="topless"><a href="Continuous-Integration.html"
|
||
title="next chapter">Continuous Integration (CI)</a></p>
|
||
</div>
|
||
<div role="note" aria-label="source link">
|
||
<!--h3>This Page</h3-->
|
||
<ul class="this-page-menu">
|
||
<li><a href="../_sources/Coding/Profiling.md.txt"
|
||
rel="nofollow">Show Page Source</a></li>
|
||
</ul>
|
||
</div><h3>Links</h3>
|
||
<ul>
|
||
<li><a href="https://www.evennia.com/docs/latest/index.html">Documentation Top</a> </li>
|
||
<li><a href="https://www.evennia.com">Evennia Home</a> </li>
|
||
<li><a href="https://github.com/evennia/evennia">Github</a> </li>
|
||
<li><a href="http://games.evennia.com">Game Index</a> </li>
|
||
<li>
|
||
<a href="https://discord.gg/AJJpcRUhtF">Discord</a> -
|
||
<a href="https://github.com/evennia/evennia/discussions">Discussions</a> -
|
||
<a href="https://evennia.blogspot.com/">Blog</a>
|
||
</li>
|
||
</ul>
|
||
<h3>Doc Versions</h3>
|
||
<ul>
|
||
|
||
<li>
|
||
<a href="https://www.evennia.com/docs/latest/index.html">latest (main branch)</a>
|
||
</li>
|
||
|
||
|
||
<li>
|
||
<a href="https://www.evennia.com/docs/5.x/index.html">v5.0.0 branch (outdated)</a>
|
||
</li>
|
||
|
||
<li>
|
||
<a href="https://www.evennia.com/docs/4.x/index.html">v4.0.0 branch (outdated)</a>
|
||
</li>
|
||
|
||
<li>
|
||
<a href="https://www.evennia.com/docs/3.x/index.html">v3.0.0 branch (outdated)</a>
|
||
</li>
|
||
|
||
<li>
|
||
<a href="https://www.evennia.com/docs/2.x/index.html">v2.0.0 branch (outdated)</a>
|
||
</li>
|
||
|
||
<li>
|
||
<a href="https://www.evennia.com/docs/1.x/index.html">v1.0.0 branch (outdated)</a>
|
||
</li>
|
||
|
||
<li>
|
||
<a href="https://www.evennia.com/docs/0.x/index.html">v0.9.5 branch (outdated)</a>
|
||
</li>
|
||
|
||
</ul>
|
||
|
||
</div>
|
||
</div>
|
||
<div class="clearer"></div>
|
||
</div>
|
||
<div class="related" role="navigation" aria-label="Related">
|
||
<h3>Navigation</h3>
|
||
<ul>
|
||
<li class="right" style="margin-right: 10px">
|
||
<a href="../genindex.html" title="General Index"
|
||
>index</a></li>
|
||
<li class="right" >
|
||
<a href="../py-modindex.html" title="Python Module Index"
|
||
>modules</a> |</li>
|
||
<li class="right" >
|
||
<a href="Continuous-Integration.html" title="Continuous Integration (CI)"
|
||
>next</a> |</li>
|
||
<li class="right" >
|
||
<a href="Unit-Testing.html" title="Unit Testing"
|
||
>previous</a> |</li>
|
||
<li class="nav-item nav-item-0"><a href="../index.html">Evennia</a> »</li>
|
||
<li class="nav-item nav-item-1"><a href="Coding-Overview.html" >Coding and development help</a> »</li>
|
||
<li class="nav-item nav-item-this"><a href="">Profiling</a></li>
|
||
</ul>
|
||
</div>
|
||
<div class="footer" role="contentinfo">
|
||
© Copyright 2024, The Evennia developer community.
|
||
Created using <a href="https://www.sphinx-doc.org/">Sphinx</a> 8.2.3.
|
||
</div>
|
||
</body>
|
||
</html> |