diff --git a/.nojekyll b/.nojekyll new file mode 100644 index 0000000..e69de29 diff --git a/0:-FAQ/index.html b/0:-FAQ/index.html new file mode 100644 index 0000000..4a68699 --- /dev/null +++ b/0:-FAQ/index.html @@ -0,0 +1,269 @@ + + + + + + + + 0: FAQ - FNA Docs + + + + + + + + + + + + + + +
+ + +
+ +
+
+ +
+
+
+
+ +

0: FAQ

+
+

How can I financially contribute to FNA's development?

+

Currently the only way is through GitHub Sponsors. 100% of your contribution goes directly to the lead maintainer (unlike competing crowdfunding services) and your payment information is securely stored and not shared with the FNA team in any way (i.e. "you don't have to give Some Random Guy your credit card information").

+

What is the difference between FNA and MonoGame?

+

Please read the documentation regarding compatibility between the two. For any deeper comparison, the only way to know the difference is to run both of them yourself.

+

I'm new to programming and-

+

Hi there! Glad you've taken an interest in programming. Please do not use FNA yet.

+

While FNA is a programming tool, it is targeted specifically at experienced programmers. If you are not familiar with C# and msbuild (via Visual Studio, MonoDevelop, or maybe just the XML format?) you will not have a good experience.

+

It is required that you learn at least the basics of the above, and also the difference between managed and native code, before using FNA.

+

I was following a tutorial from Website X and-

+

Unless it came directly from this wiki, do NOT use any third party documentation! Most tutorials are very poorly written and completely unmaintained; if there was a good tutorial we would have endorsed it on the front page of our wiki. It's rare to see a tutorial that even gets the development environment right (see below).

+

Our documentation is minimal, but straightforward for experienced C# programmers (see above).

+

What development environments are supported?

+

The only officially supported environments are the following:

+
    +
  • The environment described on Page 1
  • +
  • MonoDevelop on Fedora Workstation using the mono-project.com repository, targeting .NET Framework with FNA.csproj
  • +
  • Visual Studio 2010 or newer on Windows, targeting .NET Framework with FNA.csproj
  • +
  • Visual Studio 2019 or newer on Windows, targeting consoles with NativeAOT and FNA.Core.csproj
  • +
  • Visual Studio for Mac, targeting iOS/tvOS with FNA.Core.csproj
  • +
+

All other environments and runtimes are unsupported and should not be used.

+

Where is the NuGet package?

+

FNA and its sublibraries do not use NuGet in any capacity. We strongly recommend avoiding NuGet in general, and for FNA we recommend adding FNA.csproj (or FNA.Core.csproj) directly to your solution. FNA itself compiles almost instantly, and debug builds are incredibly valuable to have during development.

+

Any and all NuGet packages for any of our code (FNA, SDL2#, etc.) are unauthorized and should be avoided. If the package claims that we're the authors, please report the package as it is misrepresenting its authors and potentially violating the copyright license (i.e. "... must not be misrepresented as being the original software").

+

What is the FNA content system?

+

FNA supports the XNA content pipeline for preservation reasons, but we strongly recommend against using it on new projects. FNA supports loading common data formats like PNG, WAV and OGG directly, and the community maintains a few libraries for font loading and rendering. For anything more specialized you can bring in an external library or write your own processing and importing tools. Your content system does not have to be complex and there is nothing wrong with simple approaches like loading textures from PNG files.

+

How do I use shaders with FNA?

+

FNA uses Direct3D 9 Effects, to match XNA's shader content format. Using fxc.exe, ideally from the DirectX SDK:

+
fxc.exe /T fx_2_0 MyEffect.fx /Fo MyEffect.fxb
+
+

Other shader formats, languages, etc are not supported. While FXC is a Windows binary, the native d3dcompiler used to build these binaries is known to work with Wine.

+

Where is PlayStation support?

+

If a project comes along and there's money behind it, we'll do it. There are a couple pending projects already, but you should not hesitate to get in touch if you have SDK access and are willing to develop and/or fund the completion of FNA PS4/PS5.

+

Where is Android support?

+

Android is not and cannot be supported. The solution you just thought of does not work. No, not that other one either. Or even the one someone got booting, almost.

+

Internally, we have what's called a "body count" for anyone that tries to add support. It was pretty funny, at least for the first dozen increments.

+

We intend to look at Fuchsia once production devices are available. In the meantime, FNA is known to work on PinePhone with various Free mobile operating systems.

+

I get a BadImageFormatException, and-

+

You mixed up 32- and 64-bit binaries. As it turns out, Microsoft broke their CPU arch configuration in two ways:

+
    +
  1. They added a flag to AnyCPU to make it default to 32-bit even on 64-bit Windows. It's a checkbox in your project settings, uncheck it.
  2. +
  3. They made it so new projects don't actually have x86 and x64 configs, only AnyCPU, so you may have added FNA to your solution only to get an x64 entry in your platform dropdown that isn't actually x64! Feel free to keep using AnyCPU and ignore x86/x64 once you've unchecked Prefer 32-bit, but if it's any consolation you can probably just make an x64 config for your project and always use that, so there's no ambiguity as to what arch you're targeting.
  4. +
+

(Also, once you've done all this, delete bin/ and obj/. No, we don't know why you need to do this.)

+

I have a bug when running on VirtualBox, and-

+

The bug is that you are using VirtualBox. Please use VMware Player instead.

+

I have a bug when running on system Mono, and-

+

Your LD_LIBRARY_PATH is busted. You can do one of three things:

+
    +
  • Preserve the lib64 folder (like you're supposed to anyway) and set LD_LIBRARY_PATH to include that folder
  • +
  • Delete your output's copy of libSDL2-2.0.so.0, keeping the rest of the libs next to your exe, and be sure your distribution provides the latest stable SDL release (maybe don't do this one since it's not the official build)
  • +
  • Throw the fnalibs into /usr/local/lib (definitely don't do this one)
  • +
+

For shipping builds, use MonoKickstart, do NOT use system dependencies!

+

Where can I get fnalibs for CPU architecture X?

+

If it is not included in the standard fnalibs.tar.bz2 you will need to build the libraries from source. The instructions for each library can be found in their respective README files.

+

What happens if I ask a question that's answered in this wiki?

+

*Bubs voice* That'll be five dollars. No, this isn't a joke. Expect a link to the sponsors page for each of your requests.

+ +
+
+ +
+
+ +
+ +
+ +
+ + + + GitHub + + + + « Previous + + + Next » + + +
+ + + + + + + + + diff --git a/1:-Setting-Up-FNA/index.html b/1:-Setting-Up-FNA/index.html new file mode 100644 index 0000000..b21fce8 --- /dev/null +++ b/1:-Setting-Up-FNA/index.html @@ -0,0 +1,348 @@ + + + + + + + + 1: Setting Up FNA - FNA Docs + + + + + + + + + + + + + + +
+ + +
+ +
+
+ +
+
+
+
+ +

1: Setting Up FNA

+

This page has two parts: The first sets up the FNA team's recommended development environment, and the second prepares FNA itself. If you already have a development environment you like, you can skip to the Download FNA section if you want, but note that our environment prepares you for remote debugging on Steam Deck.

+

Chapter 1a: Linux Setup

+

The Linux development environment for FNA is supported on all distributions with Flatpak support, including SteamOS! (But, if you absolutely must know, the FNA team uses Fedora Workstation.)

+

You may be able to find VSCode and the .NET SDKs via apps like KDE Discover, but it's easier to get everything at once with a single portable terminal command:

+
flatpak install com.visualstudio.code org.freedesktop.Sdk.Extension.mono6 org.freedesktop.Sdk.Extension.dotnet8
+
+

This installs VSCode, Mono, and .NET 8 all at once! If it asks which version of the SDKs to install, select 24.08.

+

All that's left is to expose the .NET and Mono SDKs to VSCode's sandbox:

+
flatpak --user override --env=FLATPAK_ENABLE_SDK_EXT=mono6,dotnet8 com.visualstudio.code
+
+

Chapter 1b: Windows Setup

+

At minimum you will need to install the following software:

+ +

When setting up projects, Windows has an additional setup step. An important part of multiplatform development is filesystem case sensitivity - for example, on Linux and console platforms, "filename" is NOT the same as "FileName"! To ensure that developers follow this rule, it is recommended to make your project folders case sensitive on Windows.

+

To do this, open a Command Prompt as Administrator, then (carefully!) enter the following command:

+
fsutil.exe file SetCaseSensitiveInfo "C:\path\to\your\project" enable
+
+

(While we're on the subject, remember not to use \\ for file paths!)

+

Chapter 2: Visual Studio Code Extensions

+

After starting VSCode, hit Ctrl+Shift+X to go to the extensions view, then search for "C# Dev Kit". Install the kit from the marketplace and let it download all of the components. You should be able to clear the search results and see the various new installed extensions!

+

Lastly, search for and install the "Mono Debug" extension.

+

Chapter 3: Download and Update FNA

+

We strongly recommend using Git to download and update FNA. This tutorial will guide you through this process.

+

If you are using an official zipped release of FNA, you only need to worry about step 2.

+

Step 1: Clone FNA

+

FNA uses several Git submodules to access the source to additional libraries, such as SDL2# and FAudio. To fully download FNA, add the --recursive parameter to your git clone command:

+
git clone --recursive https://github.com/FNA-XNA/FNA
+
+

This will clone FNA, then clone all of the submodules into the appropriate locations.

+

Step 2: Download Native Libraries

+

FNA uses several native libraries for various pieces of functionality, such as window management, input, and audio output.

+

Here's what we use and why:

+
    +
  • SDL2: Used for window management, input, image I/O, etc.
  • +
  • FNA3D: Only required if you use the Graphics namespace.
  • +
  • FAudio: Only required if you use the Audio or Media namespaces.
  • +
  • Theorafile: Only required if you use VideoPlayer.
  • +
+

Currently, you can find the libraries precompiled here:

+

https://fna.flibitijibibo.com/archive/fnalibs.tar.bz2

+

This archive contains all of the native libraries for Windows, Linux, and x86_64 macOS. For arm64 macOS, iOS and tvOS, it is recommended to build libraries using these third-party build scripts.

+

Step 3: Update FNA

+

It is strongly recommended that you update at least once a month. FNA releases are always on the first of every month, so you may simply want to make a calendar reminder for yourself to redownload FNA and fnalibs.tar.bz2 at the beginning of each month.

+

To update FNA, simply enter the FNA directory and run git pull. This will update to the latest FNA version, assuming you have not made local changes that conflict with the upstream changes. If you do have local changes, store them elsewhere and update, or revert your changes. (By the way, if you really do have local changes, please let us know! We want working code in upstream, and it will make your life easier, we promise!)

+

Sometimes, FNA will update one of its submodules. When this occurs, run git submodule update --init --recursive and the submodules will fully update. Again, this assumes that you have not made local changes to the submodules.

+

Step 4: Join the FNA Discord

+

Aside from the commit log, a good place to keep an eye on major FNA changes is to join the Discord server. This is where announcements and development discussion occur; in particular, there are channels allocated for general development, XNA preservation research, private console development, and experimental platform development.

+

Chapter 4: Building Old Visual Studio Projects

+

For those using old pre-.NET Core solutions, you will want to make these changes to allow building your solution:

+
    +
  1. Right click the C# Dev Kit extension and disable it, leaving all other extensions alone
  2. +
  3. Right click the C# extension and select Extension Settings
  4. +
  5. Search for useModernDotNet, uncheck Use Modern .NET
  6. +
  7. Search for useOmnisharp, check Use Omnisharp
  8. +
  9. Create a .vscode folder next to your solution, then add a tasks.json file containing something like this:
  10. +
+
{
+    // See https://go.microsoft.com/fwlink/?LinkId=733558
+    // for the documentation about the tasks.json format
+    "version": "2.0.0",
+    "tasks": [
+        {
+            "label": "build",
+            "type": "shell",
+            "command": "msbuild",
+            "args": [
+                // Ask msbuild to generate full paths for file names.
+                "/property:GenerateFullPaths=true",
+                "/t:build",
+                // Do not generate summary otherwise it leads to duplicate errors in Problems panel
+                "/consoleloggerparameters:NoSummary",
+                "THIS_IS_YOUR_GAME.sln",
+                "/p:Configuration=Debug"
+            ],
+            "group": "build",
+            "presentation": {
+                // Reveal the output only if unrecognized errors occur.
+                "reveal": "silent"
+            },
+            // Use the standard MS compiler pattern to detect errors, warnings and infos
+            "problemMatcher": "$msCompile"
+        }
+    ]
+}
+
+

With this in place, you should now be able to build (Terminal -> Run Build Task)!

+

Chapter 5: Creating New Projects

+

Making an FNA project is relatively simple for basically every C# IDE, though the process has changed in recent years:

+

For Visual Studio Code, you can use the built-in terminal to navigate to your project folder, then run dotnet new sln --name YourProjectName to create a new solution, dotnet new console --name YourProjectName to create an empty project, and dotnet sln add YourProjectName/YourProjectName.csproj to add it to the solution. Visual Studio and MonoDevelop have New Solution wizards for creating empty C# projects.

+

Once the empty project is created, you can go to the Solution Explorer and right click the solution, click "Add Existing Project", then add FNA (FNA.NetFramework.csproj for .NET Framework and Mono, FNA.Core.csproj for .NET 8, or FNA.csproj for old Visual Studio projects). You can then right click the empty project and add FNA as a Project Reference.

+

For existing XNA projects, we recommend targeting .NET Framework and Mono for better compatibility. For new projects, we recommend .NET 8.

+

.NET Framework Fixes

+

When targeting .NET Framework via Visual Studio Code, be sure to open YourProjectName/YourProjectName.csproj and change the target framework to net4.0!

+

You'll also find that running/debugging takes a few more steps - we're lobbying to streamline this process, but if you want to get debugging ASAP, you can open the folder where your .sln file is, make a .vscode/ folder, then rip off this launch.json file and place it in the .vscode/ folder:

+
{
+    // Use IntelliSense to learn about possible attributes.
+    // Hover to view descriptions of existing attributes.
+    // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
+    "version": "0.2.0",
+    "configurations": [
+        {
+            "name": "Launch",
+            "type": "mono",
+            "request": "launch",
+            "program": "${workspaceRoot}/path/to/your/bin/Debug/net4.0/Game.exe",
+            "cwd": "${workspaceRoot}",
+            "env": {
+                "LD_LIBRARY_PATH": "${workspaceRoot}/path/to/your/fnalibs/lib64/"
+            }
+        },
+        {
+            "name": "Attach",
+            "type": "mono",
+            "request": "attach",
+            "address": "localhost",
+            "port": 55555
+        }
+    ]
+}
+
+

With this in place, you should now be able to launch the program after it's built (Run -> Start Debugging, or F5!)!

+

.NET Core Fixes

+

DllMap is a critical component of .NET portability that maps native library names to appropriate equivalents on multiple platforms. For example, while Windows might look for fmod_studio.dll, Linux will instead look for libfmodstudio.so.XX, which is nontrivial for the runtime to figure out on its own. By adding a config file like this one, we're able to automatically map DLL names for all platforms without resorting to per-platform builds with customized DLL names.

+

Shamefully, this is currently absent from .NET Core. We will continue to lobby for adding this feature back to modern .NET, but for now we have developed a workaround called FNADllMap. We strongly encourage everyone to copy this file directly and add it to every single project, particularly those that use DllImport, so that all managed EXE/DLL files have a rough equivalent of real DllMap support.

+

Visual Studio AnyCPU Fix

+

For Visual Studio 2019 users, follow these additional steps to allow VS to build your project properly for 64-bit:

+
    +
  1. In the Visual Studio toolbar, click on the Solution Platforms dropdown menu (where it says 'Any CPU'), and click on 'Configuration Manager...'
  2. +
  3. In the Configuration Manager window that appears, change the 'Active solution platform' to x64. Notice that the referenced FNA project changes to x64, but your project remains 'Any CPU'
  4. +
  5. Address this discrepancy by clicking on the platform dropdown for your project and clicking New
  6. +
  7. In the New Project Platform dialog that appears, create a new platform using x64 from the dropdown. Copying settings from Any CPU is fine, and make sure the 'Create new solution platforms' is unchecked
  8. +
+

You can now build your project for x64!

+ +
+
+ +
+
+ +
+ +
+ +
+ + + + GitHub + + + + « Previous + + + Next » + + +
+ + + + + + + + + diff --git a/2a:-Building-XNA-Games-with-FNA/index.html b/2a:-Building-XNA-Games-with-FNA/index.html new file mode 100644 index 0000000..2f4f406 --- /dev/null +++ b/2a:-Building-XNA-Games-with-FNA/index.html @@ -0,0 +1,227 @@ + + + + + + + + 2a: Building XNA Games with FNA - FNA Docs + + + + + + + + + + + + + + +
+ + +
+ +
+
+ +
+
+
+
+ +

2a: Building XNA Games with FNA

+

This is a guide to getting your existing XNA game running on FNA. If you are creating a new game, please see this wiki page.

+

Note that this entire guide can be done on Windows - you do not need a Mac or a Linux box to perform anything described on this page.

+

Also note that this is NOT strictly a guide on having your game fully ported to non-XNA platforms! FNA can only make XNA itself portable; whether or not your game is in any way portable is out of our hands. If this is a concern, look at "FNA and Windows API".

+

1. Making the FNA Solution

+

We strongly recommend making a brand new solution for the FNA version. It's possible to fiddle with your XNA solution to link your code against FNA, but it's probably just easier to make YourGame.FNA.sln and keep YourGame.sln as it is. See Page 1 for a quick refresher on how to do this!

+

One thing you will NOT need to recreate is the content projects. When deploying to FNA, your content should already be complete and built. You can keep this in your solution if you like, but you will need to reference the XNA content pipeline here. You should not reference FNA in content projects!

+

(As an aside, if you are creating new content instead of porting existing content, you should not feel pressured to use processing tools like the XNA/MonoGame content pipeline tools! It is perfectly reasonable to develop your own content system that can be designed and optimized to work well with you and your development team, and in fact that is what we recommend doing for new projects.)

+

Once you've remade your solution with all of your game's subprojects, add FNA.csproj into your solution. FNA, despite using many different C# wrappers, is just a single project file. This simplifies project generation and quickly gives you access to, for example, SDL2# if you need it in your game code.

+

Your projects' references are going to be the same as they were in XNA4, except now you will reference FNA instead of the XNA libraries.

+

Once all of the projects have been made and are linking to FNA, your solution should now compile. Simple as that!

+

2. About Content Support

+

While we do our best to support 100% of the XNA content available, there are a few notable exceptions due to both technical and legal obstacles.

+

2a. About Effect Support

+

FNA uses MojoShader to parse and rebuild Effect shaders to non-D3D graphics APIs. This allows us to use the original XNB-packed Effects built by XNA and fx_2_0 effect binaries built by FXC, the Effect compiler from the June 2010 DirectX SDK. However, there are exactly three caveats to this:

+
    +
  • We do not attempt to undo half-pixel offsets applied by the game's shaders. If you don't know what this is, don't worry. If you do know what this is and have applied them in your engine, simply remove them from the FNA version of your game and it should visually match D3D without any problems.
  • +
  • Another half-pixel-like issue present is in the VPOS attribute; D3D9 puts the VPOS at integer values while all other graphics APIs put it at half-pixel values (in contrast to the half-pixel offset described above, where it's the exact opposite). If you find that VPOS isn't acting as expected, take the input VPOS value and floor() it before doing anything else in the shader.
  • +
  • For maximum compatibility, vertex input layouts must match the vertex shader input parameters exactly. D3D9 and OpenGL would quietly drop input streams that were unused in the vertex shader, but newer graphics APIs like D3D11 and Vulkan are far more strict about this. For example, if your vertex shader expects a TEXCOORD0 input, but your VertexDeclaration does not include a TextureCoordinate with index 0, this is a hard error unless you forcibly use the OpenGL renderer exclusively.
  • +
+

2b. Rebuilding WMA/WMV Files

+

XNA uses Windows Media Player for the Song and the VideoPlayer implementations. Obviously we cannot use this in a multiplatform reimplementation, so currently we do not support WMA/WMV files. Instead, we support Ogg Vorbis and QOA for audio and Ogg Theora for video.

+

2c. Rebuilding XACT WaveBanks

+

XACT for Windows supports a codec called xWMA for highly-compressed audio streams, rather than the XMA codec typically used on the 360. Like WMA support we do not have support for either of these, but you can still compress your WaveBanks with ADPCM. The file size will likely be larger, but the functionality of your XACT code/content should be exactly the same, and no changes are required if you do not use xWMA or XMA.

+

3. Window Icons

+

Typically a Windows application will use the embedded .ico image for both the window icon and the icon used in other parts of the OS, such as the taskbar. While this does work on Windows, it actually turns out to be unusable on other platforms, so we instead use a separate bitmap file to set the process icon.

+

For macOS you don't have to worry about this; the icns file that is discussed in the "Distributing" section will cover everything there.

+

For Linux (and optionally Windows, if you want a higher-res icon), simply place a bitmap file in the game's root directory. The bitmap's filename is the same string as the window title, minus the characters that are not allowed for filenames. The recommended image size is at least 512x512, as many desktops will use this icon for more than just the 16x16 image next to the window title.

+

4. Running the FNA Output

+

Once the FNA version has been built, copy over your Content folder and native libraries (which you should have downloaded along with FNA itself) into the output folder. From there, the game should be able to run!

+

This exact output will be what you run on Linux and macOS. The only difference will be the native libraries in addition to FNA.dll.config, which should be in your output folder even if you're on Windows. FNA.dll.config is what remaps the native DLL names to the proper names of the native libraries on non-Windows operating systems. Aside from this, everything else should work - the C# assemblies, the Content, everything. Push this output to a Linux/macOS box and try them out!

+

When using a developer environment on macOS, you will want to add an environment variable that sets DYLD_LIBRARY_PATH=./osx/, so that the IDE's runtime environment will find the fnalibs binaries.

+ +
+
+ +
+
+ +
+ +
+ +
+ + + + GitHub + + + + « Previous + + + Next » + + +
+ + + + + + + + + diff --git a/2b:-Building-New-Games-with-FNA/index.html b/2b:-Building-New-Games-with-FNA/index.html new file mode 100644 index 0000000..239fd19 --- /dev/null +++ b/2b:-Building-New-Games-with-FNA/index.html @@ -0,0 +1,747 @@ + + + + + + + + 2b: Building New Games with FNA - FNA Docs + + + + + + + + + + + + + + +
+ + +
+ +
+
+ +
+
+
+
+ +

2b: Building New Games with FNA

+

Before You Start

+

This is strictly a tutorial about using FNA. It is NOT a C# tutorial! If you are learning C# for the first time, use Microsoft's official Introduction to C# on MSDN first before continuing on.

+

Also, be sure to finish Page 1 before starting this page!

+

What is XNA?

+

XNA was, at its core, the software equivalent of an 80's Saturday morning cartoon based on a toy line: A massive advertisement masquerading as a real product. It was built to advertise many new (at the time) products in development at Microsoft:

+
    +
  • C# 2.0
  • +
  • Direct3D Effects Framework
  • +
  • XACT Audio Creation Tool
  • +
  • XInput and the Xbox 360 Controller
  • +
  • Xbox 360 + Windows Media Center
  • +
+

From 2006 to 2010, Microsoft maintained XNA as a means of allowing independent game developers to ship small games written in C# on Xbox 360, via the "Xbox Live Indie Games" marketplace. The final XNA release also supported building for Windows Phone 7 devices.

+

As for the XNA API, it was largely a C# wrapper for various DirectX components, but not quite all of them - many features are unavailable in favor of the aforementioned new-fangled DirectX products. For example, while there is a GraphicsDevice class that effectively acts as a 1:1 map of ID3D10Device, notably missing is support for low-level shaders and constant buffers; instead you are expected to use Effects for shader support.

+

XNA was officially discontinued in 2012, and the Xbox Live Indie Games marketplace was shut down on November 2017.

+

What is FNA?

+

FNA is a preservation project designed to accurately reimplement the XNA runtime libraries. When you have an XNA game, you should be able to take the source, compile it against FNA, and have a fully-functioning port. At its core, FNA is a portability library, but many continue to develop new games with FNA. This tutorial will help you make your own FNA games, without needing XNA as a prerequisite. If you are bringing an existing XNA game to FNA, follow this wiki page instead.

+

Your First Game

+

See Page 1 for a quick refresher on making new projects. Once you have a project made, you can then proceed:

+

The First Program

+

This is the smallest possible program using the framework portion of XNA:

+
using System;
+using Microsoft.Xna.Framework;
+
+static class Program
+{
+    [STAThread]
+    static void Main(string[] args)
+    {
+        using (Game g = new Game())
+        {
+            new GraphicsDeviceManager(g);
+            g.Run();
+        }
+    }
+}
+
+

This should compile into a folder like bin/Debug/. Next to your executable, you will put the native libraries you downloaded earlier into this folder. You only need to worry about the libraries for your development platform; the rest will be for when you distribute your game. For example, if you're building an AnyCPU program on Windows x64, you would take the contents of the native library archive's x64 folder and put them next to your exe.

+

When using a developer environment on macOS, you will want to add an environment variable that sets DYLD_LIBRARY_PATH=./osx/ (or wherever your dylib files are), so that the IDE's runtime environment will find the fnalibs binaries.

+

When running this program, you might see some random trash in the window; that is most likely old graphics memory from another program you were running. Aside from that, the game is fully functional; it is reading input, running updates, and presenting frames to the window. But if this is the whole program, where do we put the rest of the game?

+

The First Game Object

+

The trick is that you're not going to create a Game directly. Instead, you're going to inherit it!

+
using System;
+using Microsoft.Xna.Framework;
+
+class FNAGame : Game
+{
+    [STAThread]
+    static void Main(string[] args)
+    {
+        using (FNAGame g = new FNAGame())
+        {
+            g.Run();
+        }
+    }
+
+    private FNAGame()
+    {
+        // This gets assigned to something internally, don't worry...
+        new GraphicsDeviceManager(this);
+    }
+}
+
+

But again, there's still no place to put the game. That's because Game has several protected methods that you are meant to implement. Here's what it looks like with the most commonly-used methods:

+
using System;
+using Microsoft.Xna.Framework;
+
+class FNAGame : Game
+{
+    [STAThread]
+    static void Main(string[] args)
+    {
+        using (FNAGame g = new FNAGame())
+        {
+            g.Run();
+        }
+    }
+
+    private FNAGame()
+    {
+        GraphicsDeviceManager gdm = new GraphicsDeviceManager(this);
+
+        // Typically you would load a config here...
+        gdm.PreferredBackBufferWidth = 1280;
+        gdm.PreferredBackBufferHeight = 720;
+        gdm.IsFullScreen = false;
+        gdm.SynchronizeWithVerticalRetrace = true;
+    }
+
+    protected override void Initialize()
+    {
+        /* This is a nice place to start up the engine, after
+         * loading configuration stuff in the constructor
+         */
+        base.Initialize();
+    }
+
+    protected override void LoadContent()
+    {
+        // Load textures, sounds, and so on in here...
+        base.LoadContent();
+    }
+
+    protected override void UnloadContent()
+    {
+        // Clean up after yourself!
+        base.UnloadContent();
+    }
+
+    protected override void Update(GameTime gameTime)
+    {
+        // Run game logic in here. Do NOT render anything here!
+        base.Update(gameTime);
+    }
+
+    protected override void Draw(GameTime gameTime)
+    {
+        // Render stuff in here. Do NOT run game logic in here!
+        GraphicsDevice.Clear(Color.CornflowerBlue);
+        base.Draw(gameTime);
+    }
+}
+
+

The First Input

+

It's not a game without input, right? FNA exposes GamePad, Keyboard, and Mouse for user input. There is also a Microsoft.Xna.Framework.Input.Touch namespace for touch screen support.

+

Input isn't terribly complicated; you store two copies of input state, one for current input and another for previous input. This lets you detect presses and releases, in addition to just checking for a button being down:

+
using System;
+using Microsoft.Xna.Framework;
+using Microsoft.Xna.Framework.Input;
+
+class FNAGame : Game
+{
+    [STAThread]
+    static void Main(string[] args)
+    {
+        using (FNAGame g = new FNAGame())
+        {
+            g.Run();
+        }
+    }
+
+    private KeyboardState keyboardPrev = new KeyboardState();
+    private MouseState mousePrev = new MouseState();
+    private GamePadState gpPrev = new GamePadState();
+
+    private FNAGame()
+    {
+        new GraphicsDeviceManager(this);
+    }
+
+    protected override void Update(GameTime gameTime)
+    {
+        // Poll input
+        KeyboardState keyboardCur = Keyboard.GetState();
+        MouseState mouseCur = Mouse.GetState();
+        GamePadState gpCur = GamePad.GetState(PlayerIndex.One);
+
+        // Check for presses
+        if (keyboardCur.IsKeyDown(Keys.Space) && keyboardPrev.IsKeyUp(Keys.Space))
+        {
+            System.Console.WriteLine("Space bar was pressed!");
+        }
+        if (mouseCur.RightButton == ButtonState.Released && mousePrev.RightButton == ButtonState.Pressed)
+        {
+            System.Console.WriteLine("Right mouse button was released!");
+        }
+        if (gpCur.Buttons.A == ButtonState.Pressed && gpPrev.Buttons.A == ButtonState.Pressed)
+        {
+            System.Console.WriteLine("A button is being held!");
+        }
+
+        // Current is now previous!
+        keyboardPrev = keyboardCur;
+        mousePrev = mouseCur;
+        gpPrev = gpCur;
+
+        base.Update(gameTime);
+    }
+
+    protected override void Draw(GameTime gameTime)
+    {
+        GraphicsDevice.Clear(Color.CornflowerBlue);
+        base.Draw(gameTime);
+    }
+}
+
+

Be sure to read all of the input APIs for more details! You may also be interested in some extensions to the XNA spec that improve input support in FNA.

+

The First Sprite

+

Finally, some graphics! In XNA, there is a class called SpriteBatch that makes sprite drawing relatively easy. Combine that with your own textures and you have the foundation of a 2D renderer.

+

This sample loads a PNG named "FNATexture", located in a "Content" folder, and renders it with a SpriteBatch:

+
using System;
+using Microsoft.Xna.Framework;
+using Microsoft.Xna.Framework.Graphics;
+
+class FNAGame : Game
+{
+    [STAThread]
+    static void Main(string[] args)
+    {
+        using (FNAGame g = new FNAGame())
+        {
+            g.Run();
+        }
+    }
+
+    private SpriteBatch batch;
+    private Texture2D texture;
+
+    private FNAGame()
+    {
+        new GraphicsDeviceManager(this);
+
+        // All content loaded will be in a "Content" folder
+        Content.RootDirectory = "Content";
+    }
+
+    protected override void LoadContent()
+    {
+        // Create the batch...
+        batch = new SpriteBatch(GraphicsDevice);
+
+        // ... then load a texture from ./Content/FNATexture.png
+        texture = Content.Load<Texture2D>("FNATexture");
+    }
+
+    protected override void UnloadContent()
+    {
+        batch.Dispose();
+        texture.Dispose();
+    }
+
+    protected override void Draw(GameTime gameTime)
+    {
+        GraphicsDevice.Clear(Color.CornflowerBlue);
+
+        // Draw the texture to the corner of the screen
+        batch.Begin();
+        batch.Draw(texture, Vector2.Zero, Color.White);
+        batch.End();
+
+        base.Draw(gameTime);
+    }
+}
+
+

If all went well, the PNG you chose should now be displayed! When drawing sprites, be absolutely sure that you draw as many as you possibly can before calling End(); batches are meant to be large, singular groups rather than lots of small, fragmented groups. The more you put in a single batch, the better your program will perform.

+

And whatever you do, do NOT use SpriteSortMode.Immediate!

+

The First Sound

+

In addition to XACT, there is also a SoundEffect API available. It's as simple as loading a .wav file and mashing Play():

+
using System;
+using Microsoft.Xna.Framework;
+using Microsoft.Xna.Framework.Audio;
+using Microsoft.Xna.Framework.Input;
+
+class FNAGame : Game
+{
+    [STAThread]
+    static void Main(string[] args)
+    {
+        using (FNAGame g = new FNAGame())
+        {
+            g.Run();
+        }
+    }
+
+    private SoundEffect sound;
+    private KeyboardState keyboardPrev = new KeyboardState();
+
+    private FNAGame()
+    {
+        new GraphicsDeviceManager(this);
+
+        // All content loaded will be in a "Content" folder
+        Content.RootDirectory = "Content";
+    }
+
+    protected override void LoadContent()
+    {
+        // Sound is ./Content/FNASound.wav
+        sound = Content.Load<SoundEffect>("FNASound");
+    }
+
+    protected override void UnloadContent()
+    {
+        sound.Dispose();
+    }
+
+    protected override void Update(GameTime gameTime)
+    {
+        KeyboardState keyboardCur = Keyboard.GetState();
+
+        if (keyboardCur.IsKeyDown(Keys.Space) && keyboardPrev.IsKeyUp(Keys.Space))
+        {
+            sound.Play();
+        }
+
+        keyboardPrev = keyboardCur;
+    }
+
+    protected override void Draw(GameTime gameTime)
+    {
+        GraphicsDevice.Clear(Color.CornflowerBlue);
+        base.Draw(gameTime);
+    }
+}
+
+

There is lots of deeper functionality, including instance management, 3D audio APIs, and even a streaming sound object, useful for streaming from larger files (for example, sending decoded data from an Ogg Vorbis music file).

+

The First Song

+

XNA includes a Media namespace, which includes support for basic playback of music and video files. FNA supports Ogg Vorbis and QOA for the Song implementation:

+
using System;
+using Microsoft.Xna.Framework;
+using Microsoft.Xna.Framework.Media;
+
+class FNAGame : Game
+{
+    [STAThread]
+    static void Main(string[] args)
+    {
+        using (FNAGame g = new FNAGame())
+        {
+            g.Run();
+        }
+    }
+
+    private Song song;
+
+    private FNAGame()
+    {
+        new GraphicsDeviceManager(this);
+
+        // All content loaded will be in a "Content" folder
+        Content.RootDirectory = "Content";
+    }
+
+    protected override void LoadContent()
+    {
+        // Song is ./Content/FNASong.ogg
+        song = Content.Load<Song>("FNASong");
+    }
+
+    protected override void UnloadContent()
+    {
+        song.Dispose();
+    }
+
+    protected override void Update(GameTime gameTime)
+    {
+        // Just keep playing the song over and over
+        if (MediaPlayer.State == MediaState.Stopped)
+        {
+            MediaPlayer.Play(song);
+        }
+        base.Update(gameTime);
+    }
+
+    protected override void Draw(GameTime gameTime)
+    {
+        GraphicsDevice.Clear(Color.CornflowerBlue);
+        base.Draw(gameTime);
+    }
+}
+
+

The First Video

+

Video objects are a fair bit more involved than Song. In addition to playing the sound, a VideoPlayer will provide the frames in the form of a texture, which you can then render however you like:

+
using System;
+using Microsoft.Xna.Framework;
+using Microsoft.Xna.Framework.Media;
+using Microsoft.Xna.Framework.Graphics;
+
+class FNAGame : Game
+{
+    [STAThread]
+    static void Main(string[] args)
+    {
+        using (FNAGame g = new FNAGame())
+        {
+            g.Run();
+        }
+    }
+
+    private GraphicsDeviceManager gdm;
+    private Video video;
+    private VideoPlayer videoPlayer;
+    private SpriteBatch batch;
+
+    private FNAGame()
+    {
+        gdm = new GraphicsDeviceManager(this);
+
+        // All content loaded will be in a "Content" folder
+        Content.RootDirectory = "Content";
+    }
+
+    protected override void LoadContent()
+    {
+        // Video is ./Content/FNAVideo.ogv
+        video = Content.Load<Video>("FNAVideo");
+        batch = new SpriteBatch(GraphicsDevice);
+
+        gdm.PreferredBackBufferWidth = video.Width;
+        gdm.PreferredBackBufferHeight = video.Height;
+        gdm.ApplyChanges();
+
+        // Just loop the video over and over
+        videoPlayer = new VideoPlayer();
+        videoPlayer.IsLooped = true;
+        videoPlayer.Play(video);
+    }
+
+    protected override void UnloadContent()
+    {
+        batch.Dispose();
+        videoPlayer.Dispose();
+        video = null;
+    }
+
+    protected override void Draw(GameTime gameTime)
+    {
+        // Draw the video frame to the window, which should be the same size
+        batch.Begin(SpriteSortMode.Deferred, BlendState.Opaque);
+        batch.Draw(videoPlayer.GetTexture(), Vector2.Zero, Color.White);
+        batch.End();
+        base.Draw(gameTime);
+    }
+}
+
+

The First Save

+

XNA provides two filesystem APIs: TitleContainer and StorageContainer. TitleContainer is how you should open files in the game folder (provided Content.Load() does not do what you want), and is pretty much exactly the same as File.OpenRead. StorageContainer is a lot more involved, however:

+
using System;
+using Microsoft.Xna.Framework.Storage;
+
+void DoStorageContainerThing()
+{
+    IAsyncResult result;
+
+    result = StorageDevice.BeginShowSelector(null, null);
+    while (!result.IsCompleted)
+    {
+        // Just hang out for a bit...
+        System.Threading.Thread.Sleep(1);
+    }
+    StorageDevice device = StorageDevice.EndShowSelector(result);
+
+    result = device.BeginOpenContainer("SaveData", null, null);
+    while (!result.IsCompleted)
+    {
+        // Just hang out for a bit...
+        System.Threading.Thread.Sleep(1);
+    }
+    StorageContainer container = device.EndOpenContainer(result);
+
+    // Do stuff!
+
+    // Clean up after yourself! Maybe keep `device` from getting collected.
+    container.Dispose();
+}
+
+

From there, container's API is self-explanatory. There are Create/Delete/Exists/Open/GetNames APIs for directories and files. Pretty much what you'd expect!

+

The container's path is $SAVELOC/$CONTAINERNAME/$PLAYERINDEX:

+
    +
  • $SAVELOC looks something like this... and before you ask, yes, XNA really based the save folder on the EXE name:
  • +
+
string platform = SDL.SDL_GetPlatform();
+string exeName = Path.GetFileNameWithoutExtension(
+    AppDomain.CurrentDomain.FriendlyName
+).Replace(".vshost", "");
+if (platform.Equals("Windows"))
+{
+    return Path.Combine(
+        Environment.GetFolderPath(
+            Environment.SpecialFolder.MyDocuments
+        ),
+        "SavedGames",
+        exeName
+    );
+}
+if (platform.Equals("Mac OS X"))
+{
+    string osConfigDir = Environment.GetEnvironmentVariable("HOME");
+    if (String.IsNullOrEmpty(osConfigDir))
+    {
+        return "."; // Oh well.
+    }
+    return Path.Combine(
+        osConfigDir,
+        "Library/Application Support",
+        exeName
+    );
+}
+if (    platform.Equals("Linux") ||
+    platform.Equals("FreeBSD") ||
+    platform.Equals("OpenBSD") ||
+    platform.Equals("NetBSD")   )
+{
+    string osConfigDir = Environment.GetEnvironmentVariable("XDG_DATA_HOME");
+    if (String.IsNullOrEmpty(osConfigDir))
+    {
+        osConfigDir = Environment.GetEnvironmentVariable("HOME");
+        if (String.IsNullOrEmpty(osConfigDir))
+        {
+            return "."; // Oh well.
+        }
+        osConfigDir += "/.local/share";
+    }
+    return Path.Combine(osConfigDir, exeName);
+}
+return SDL.SDL_GetPrefPath(null, exeName);
+
+
    +
  • $CONTAINERNAME is the name you passed to BeginOpenContainer.
  • +
  • $PLAYERINDEX is either AllPlayers if you didn't pass a PlayerIndex, or Player1 through Player4.
  • +
+

The First Effect

+

NOTE: This is an advanced subject! You may want to read the official Effects documentation first.

+

XNA and FNA use Direct3D Effects for shader support. Effects are groups of HLSL shaders bundled together into one file, which can be executed in separate subgroups called "techniques" and "passes". The XNA API is slightly dumbed down compared to the stock Effects API:

+
using Microsoft.Xna.Framework.Graphics;
+
+// Effects can be loaded as content!
+Effect effect = Content.Load<Effect>("FNAEffect");
+
+// You can set parameters...
+effect.Parameters["MadeUpParameter"].SetValue(0.0f);
+
+// Set techniques...
+effect.CurrentTechnique = effect.Techniques["MadeUpTechnique"];
+
+// ... and then render each pass in the technique!
+foreach (EffectPass p in effect.CurrentTechnique.Passes)
+{
+    p.Apply(); // Sets the shaders, passes the parameters
+    GraphicsDevice.DrawIndexedPrimitives(...);
+}
+
+// Clean up after yourself!
+effect.Dispose();
+
+

XNA has multiple effects built in, for those who just want basic rendering without having to write shaders. Examples include BasicEffect, AlphaTestEffect, and EnvironmentMapEffect.

+

When writing your own Effects, you must precompile them first. This is done with FXC, the Microsoft DirectX Shader Compiler, which you can find in the DirectX SDK. To compile .fx files:

+
fxc.exe /T fx_2_0 FNAEffect.fx /Fo FNAEffect.fxb
+
+

Note that FXC works with Wine, so on Linux and macOS you can still develop shaders by calling wine fxc.exe.

+

To see some examples of .fx files, look at the stock effects!

+ +
+
+ +
+
+ +
+ +
+ +
+ + + + GitHub + + + + « Previous + + + Next » + + +
+ + + + + + + + + diff --git a/3:-Distributing-FNA-Games/index.html b/3:-Distributing-FNA-Games/index.html new file mode 100644 index 0000000..0e1e5c5 --- /dev/null +++ b/3:-Distributing-FNA-Games/index.html @@ -0,0 +1,447 @@ + + + + + + + + 3: Distributing FNA Games - FNA Docs + + + + + + + + + + + + + + +
+ + +
+ +
+
+ +
+
+
+
+ +

3: Distributing FNA Games

+

At some point you will likely want to ship your FNA game to customers, so here's a guide to shipping your game in a way that is hassle-free for players.

+

Note that this guide is for .NET Framework and Mono applications - for those using .NET 8, refer to the .NET Core section of this page.

+
+

Overall System Requirements

+

While we can't automatically determine CPU/RAM/Storage requirements (that's up to you!), we can provide a reasonably accurate requirement list for the following specs:

+
Windows:
+OS: Windows 7, fully updated
+Graphics (Minimum): Direct3D 11 support (feature level 10_0)
+Graphics (Recommended): Vulkan support
+
+Linux:
+OS: glibc 2.28+, 64-bit only
+Graphics (Minimum): OpenGL 3.0+ support (2.1 with ARB extensions acceptable)
+Graphics (Recommended): Vulkan support
+
+macOS:
+OS: 10.9 Mavericks and newer
+Graphics (Minimum): OpenGL 3.0+ support (2.1 with ARB extensions acceptable)
+Graphics (Recommended): Metal support
+
+Other: SDL_GameController devices fully supported
+
+
+

Windows

+

Because you're no longer shipping with XNA, you no longer need to provide the XNA4 redist package. Additionally, as of the latest SDL revision, we no longer require the DirectX redist to run correctly.

+

However, as you might expect, you still need to include the appropriate .NET Framework installer. Match the version that you're targeting in VS and you're good to go.

+

The native libraries needed by FNA are in the fnalibs.tar.bz2 package under the x86/ folder. The x64/ folder will only apply if you add 64-bit support to your Windows version.

+
+

GNU/Linux

+

Download the latest version of MonoKickstart:

+

https://github.com/flibitijibibo/MonoKickstart

+

You only need the latest revision; it is actually not recommended to download using Git, as the repository is mostly binary blobs, so the download time will be much longer.

+

In the precompiled/ folder you will notice the following:

+
    +
  • kick.bin.x86_64, kick.bin.osx, monoconfig, monomachineconfig
      +
    • For Linux, you care about all of these except kick.bin.osx
    • +
    +
  • +
  • Lots and lots of DLL files.
      +
    • If you don't know which ones you need, just use them all.
    • +
    +
  • +
+

What you're going to do is place the game itself into the same folder as these Kick/DLL files, and you're also going to put the lib64/ folder (NOT ITS CONTENTS) from the fnalibs.tar.bz2 package next to your game files. Any other native libaries you have will also go into the lib64/ folder (for example, if you're using Steamworks.NET, you would put libsteam_api.so in that folder).

+

These files you're looking at are a highly compacted Mono runtime that will be executing the C# assemblies, just as .NET would on Windows. The upside is, there are no system dependencies - the whole runtime is in this one folder, and all the native dependencies are in the lib folder. Convenient!

+

However, note that not every single DLL in the C# standard library exists in this folder. Libs like System.Web.Services are not provided by default to save disk space, but if you need these you can just grab these from any Mono runtime and we'll recognize it. These libs are typically found in the lib/mono/4.x/ folder (the precompiled folder uses 4.5).

+

kick.bin.x86_64 is going to be renamed to the name of your main EXE file. For example:

+
flibitGame.exe
+flibitGame.bin.x86_64
+
+

You can optionally name it just flibitGame with no extension, if you prefer that for whatever reason.

+

Once this is finished, you are ready to upload via SteamPipe, butler, or the GOG Galaxy builder.

+

Steam Deck Remote Debugging

+

Using Visual Studio Code and the above packaging process, it's actually possible to attach to a Steam Deck for remote C# debugging! The process is as follows:

+
    +
  1. Set up the Steam Deck devkit and upload your game, complete with debug symbols (see Valve's documentation, you can ignore anything involving Proton as it's not used here!)
  2. +
  3. On the Deck, go to your new devkit game and click ⚙️ -> Properties
  4. +
  5. Under Shortcut -> Launch Options, enter the following excruciatingly long text:
  6. +
+
MONO_BUNDLED_OPTIONS='--debugger-agent=address=0.0.0.0:55555,transport=dt_socket,server=y --debug=mdb-optimizations' %command%
+
+

We're hoping to streamline this step and have sent a patch to Valve for review.

+
    +
  1. Add a task in launch.json to connect to the Deck debug server:
  2. +
+

+        {
+            "name": "Attach to Deck",
+            "type": "mono",
+            "request": "attach",
+            "address": "192.168.1.yoursteamdeckIP",
+            "port": 55555
+        }
+
+
    +
  1. Launch the game and start the debugger, enjoy!
  2. +
+

Single-Assembly Portability and Steam

+

When leveraging FNA's single-assembly portability, you can run a single binary on both Windows and Linux. For distribution, you still have to make two separate packages of roughly the same game.

+

With Steam, however, there is a way to optimize this. If you architect your depots in the following manner...

+
Depot 4206901 - Shared Content, Windows + SteamOS + Linux
+Depot 4206902 - Windows Depot, Windows
+Depot 4206903 - Linux Depot, SteamOS + Linux
+
+

The idea is that you upload the Windows fnalibs to the second depot, the Linux fnalibs and MonoKickstart environment to the third depot, then upload the entire rest of the game to the first depot. You can also put OS-specific binaries in their appropriate folders, if applicable (C# Steamworks wrappers like Steamworks.NET and Facepunch.Steamworks are the only example these days, but who knows).

+

Because the folder layout is identical between the two, this means you can limit your upload process to a single depot, only updating the other two depots when updating FNA specifically. This dramatically reduces the workload and also reduces the chance for version sync issues! Sadly this only applies to Steam; itch and GOG do not have this feature.

+

A good publicly-available example of this layout is the PC version of I MAED A GAM3 W1TH Z0MB1ES 1NIT!!!1.

+
+

macOS

+

Download the latest version of MonoKickstart:

+

https://github.com/flibitijibibo/MonoKickstart

+

You only need the latest revision; it is actually not recommended to download using Git, as the repository is mostly binary blobs, so the download time will be much longer.

+

In the precompiled/ folder you will notice the following:

+
    +
  • kick.bin.osx, kick.bin.x86_64, monoconfig, monomachineconfig
      +
    • For macOS, you care about all of these except kick.bin.x86_64
    • +
    +
  • +
  • Lots and lots of DLL files.
      +
    • If you don't know which ones you need, just use them all.
    • +
    +
  • +
+

What follows is really convoluted and annoying, because that's the Apple Way:

+

You'll start by making a series of seemingly-arbitrary folders that will look something like this:

+
flibitGame.app/
+    Contents/
+        MacOS/
+        Resources/
+
+

Next, you will put kick.bin.osx into the MacOS/ folder and rename it to the name of your main EXE. For example, for flibitGame.exe you will name it flibitGame, no extension. Next to that you will put the osx/ folder (NOT ITS CONTENTS) from the fnalibs.tar.bz2 package. The vulkan/ folder from fnalibs.tar.bz2 will go in the Resources/ folder. Any other native libraries you have will also go in the osx/ folder (for example, if you're using Steamworks.NET, you would put libsteam_api.dylib in the osx/ folder). Lastly, if you're shipping on Steam, you will put your steam_appid.txt file in Resources/.

+

So now your bundle should look like this:

+
flibitGame.app/
+    Contents/
+        MacOS/
+            flibitGame
+            osx/
+        Resources/
+            vulkan/
+            steam_appid.txt
+
+

Now, onto the Resources/ folder. You will put monoconfig, monomachineconfig, and the DLL files into this folder.

+

Those DLL files, config files, and kick.bin.osx are actually a highly compacted Mono runtime that will be executing the C# assemblies, just as .NET would on Windows. The upside is, there are no system dependencies - the whole runtime is in this one folder, and all the native dependencies are in the lib folder. Convenient!

+

However, note that not every single DLL in the C# standard library exists in this folder. Libs like System.Web.Services are not provided by default to save disk space, but if you need these you can just grab these from any Mono runtime and we'll recognize it. These libs are typically found in the lib/mono/4.x/ folder (the precompiled folder uses 4.5).

+

Finally, you will put your whole game into the Resources/ folder. After that, the bundle should look like this:

+
flibitGame.app/
+    Contents/
+        MacOS/
+            flibitGame
+            osx/
+            steam_appid.txt
+        Resources/
+            vulkan/
+            monoconfig
+            monomachineconfig
+            mscorlib.dll, System.dll, blah blah
+            flibitGame.exe
+            Content/
+            etc...
+
+

At this point, we're now ready for the Mac-specific data. No, seriously, we haven't even gotten to that part yet.

+

First, you will need to put a .icns file in the Resources/ folder. An icns file can be generated with any image using this website:

+

https://cloudconvert.com/png-to-icns

+

It is strongly recommended that you use an image that is at least 512x512 in size. For certification, Apple actually requires a 4096x4096 image for your icon!

+

The very last file that will be made before your bundle is done is an Info.plist file, which will go in the Contents/ folder. Here's an example Info.plist file:

+
<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+    <key>CFBundleDevelopmentRegion</key>
+    <string>en</string>
+    <key>CFBundleExecutable</key>
+    <string>EscapeGoat2</string>
+    <key>CFBundleIconFile</key>
+    <string>EscapeGoat2</string>
+    <key>CFBundleIdentifier</key>
+    <string>com.magicaltimebean.Bastille2</string>
+    <key>CFBundleInfoDictionaryVersion</key>
+    <string>6.0</string>
+    <key>CFBundleName</key>
+    <string>Escape Goat 2</string>
+    <key>CFBundlePackageType</key>
+    <string>APPL</string>
+    <key>CFBundleShortVersionString</key>
+    <string>1.0</string>
+    <key>CFBundleSignature</key>
+    <string>GOAT</string>
+    <key>CFBundleVersion</key>
+    <string>1</string>
+    <key>LSApplicationCategoryType</key>
+    <string>public.app-category.games</string>
+    <key>LSMinimumSystemVersion</key>
+    <string>10.9</string>
+    <key>NSHumanReadableCopyright</key>
+    <string>Copyright © 2014 MagicalTimeBean. All rights reserved.</string>
+    <key>NSPrincipalClass</key>
+    <string>NSApplication</string>
+    <key>NSHighResolutionCapable</key>
+    <string>True</string>
+</dict>
+</plist>
+
+

With that, the final look of the bundle:

+
flibitGame.app/
+    Contents/
+        Info.plist
+        MacOS/
+            flibitGame
+            osx/
+        Resources/
+            vulkan/
+            steam_appid.txt
+            monoconfig
+            monomachineconfig
+            mscorlib.dll, System.dll, blah blah
+            flibitGame.exe
+            flibitGame.icns
+            Content/
+            etc...
+
+

Once you've compiled all of this together, you should have a working app bundle! FINALLY! Place your app bundle and any other items you want to include with your game into a folder, then you are ready to upload via SteamPipe, butler, or the GOG Galaxy builder.

+

One extra note: If for some reason you want to codesign your app (this is optional), you will want to have this in an entitlements.plist file when signing:

+
<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+    <key>com.apple.security.app-sandbox</key>
+    <true/>
+    <key>com.apple.security.automation.apple-events</key>
+    <true/>
+    <key>com.apple.security.cs.allow-dyld-environment-variables</key>
+    <true/>
+    <key>com.apple.security.cs.allow-jit</key>
+    <true/>
+    <key>com.apple.security.cs.allow-unsigned-executable-memory</key>
+    <true/>
+    <key>com.apple.security.cs.disable-executable-page-protection</key>
+    <true/>
+    <key>com.apple.security.cs.disable-library-validation</key>
+    <true/>
+    <key>com.apple.security.device.usb</key>
+    <true/>
+</dict>
+</plist>
+
+

When signing for Steam, the first key should be false, otherwise it won't be able to detect when Steam is running.

+

.NET Core

+

The above guide works for .NET Framework and Mono applications, but does not work with .NET 8. The publishing system for modern .NET has completely changed and is described below.

+

dotnet publish -r <win-x64/linux-x64/osx-x64> -c Release --self-contained will produce the executable package, but each platform has different requirements for where the fnalibs must be placed.

+
    +
  • Windows: Place the x64 fnalibs in the publish directory alongside your executable.
  • +
  • MacOS: Place the osx fnalibs in the publish directory alongside your executable. Then use install_name_tool -add_rpath @executable_path <your_app_executable_name> to force the application to first look in the executable directory for the fnalibs, instead of /usr/local/lib.
  • +
  • Linux: Place the lib64 fnalibs in the publish directory, in a sub-directory called netcoredeps.
  • +
+

Single-File Applications

+

The above steps for publishing will produce a publish directory with an absolutely enormous amount of DLLs. If you want to build a single-file executable instead, just add this to a property group in your .csproj:

+
<PublishSingleFile>true</PublishSingleFile>
+
+

However, if you do this, we request that you make an exception for FNA.dll so that it is not bundled into the exe like the rest of the app. This is not required, but it is beneficial for both end users and FNA developers, since it allows for dynamically swapping out the FNA.dll in the game's files (for debugging, modding, etc.).

+

You can prevent FNA.dll from being bundled by changing the FNA ProjectReference in your game's .csproj to the following:

+
  <ItemGroup>
+    <ProjectReference Include="path/to/FNA.Core.csproj">
+      <ExcludeFromSingleFile>true</ExcludeFromSingleFile>
+    </ProjectReference>
+  </ItemGroup>
+
+

On a similar note, please do not bundle the native fnalibs into the single-file executable, for the same reasons. (Don't worry, this will not happen unless you go out of your way to explicitly include them in the project.)

+ +
+
+ +
+
+ +
+ +
+ +
+ + + + GitHub + + + + « Previous + + + Next » + + +
+ + + + + + + + + diff --git a/404.html b/404.html new file mode 100644 index 0000000..ccaf2b6 --- /dev/null +++ b/404.html @@ -0,0 +1,165 @@ + + + + + + + + FNA Docs + + + + + + + + + + + + +
+ + +
+ +
+
+
    +
  • +
  • +
  • +
+
+
+
+
+ + +

404

+ +

Page not found

+ + +
+
+ +
+
+ +
+ +
+ +
+ + + + GitHub + + + + + +
+ + + + + + + + + diff --git a/4:-FNA-and-Windows-API/index.html b/4:-FNA-and-Windows-API/index.html new file mode 100644 index 0000000..8c0419a --- /dev/null +++ b/4:-FNA-and-Windows-API/index.html @@ -0,0 +1,448 @@ + + + + + + + + 4: FNA and Windows API - FNA Docs + + + + + + + + + + + + + + +
+ + +
+ +
+
+ +
+
+
+
+ +

4: FNA and Windows API

+

While we have set out to make our XNA reimplementation as portable as possible, this work can only ever make XNA itself portable. We cannot make your game portable automatically!

+

Here are some common things we've seen that will get in the way of making your game 100% portable:

+ +
+

64-bit Support

+

Unlike XNA, FNA supports both 64-bit and AnyCPU configurations. On Linux and macOS this does not really matter, as the Mono CLR does not care what the architecture is for managed binaries and MonoKickstart automatically picks the right library folder, but on Windows each target architecture needs its own version. These days you can safely assume 64-bit only, but if you absolutely require AnyCPU (be careful, modern Visual Studio releases will still prefer 32-bit for AnyCPU builds anyway!) you should add this code to the start of your Main function:

+
using System;
+using System.IO;
+using System.Runtime.InteropServices;
+
+...
+
+[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
+[return: MarshalAs(UnmanagedType.Bool)]
+static extern bool SetDefaultDllDirectories(int directoryFlags);
+
+[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
+static extern void AddDllDirectory(string lpPathName);
+
+[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
+[return: MarshalAs(UnmanagedType.Bool)]
+static extern bool SetDllDirectory(string lpPathName);
+
+const int LOAD_LIBRARY_SEARCH_DEFAULT_DIRS = 0x00001000;
+
+...
+
+static void Main(string[] args)
+{
+    if (Environment.OSVersion.Platform == PlatformID.Win32NT)
+    {
+        try
+        {
+            SetDefaultDllDirectories(LOAD_LIBRARY_SEARCH_DEFAULT_DIRS);
+            AddDllDirectory(Path.Combine(
+                AppDomain.CurrentDomain.BaseDirectory,
+                Environment.Is64BitProcess ? "x64" : "x86"
+            ));
+        }
+        catch
+        {
+            // Pre-Windows 7, KB2533623 
+            SetDllDirectory(Path.Combine(
+                AppDomain.CurrentDomain.BaseDirectory,
+                Environment.Is64BitProcess ? "x64" : "x86"
+            ));
+        }
+    }
+
+    ...
+}
+
+

With this you can keep the C# binaries at the top level, and the native libraries can be in x86 and x64 separately (we do this for you already in fnalibs.tar.bz2).

+

DirectInput Support

+

If you have a specialized DirectInput path for your XNA game, go ahead and take it out of your FNA build. FNA uses SDL_GameController for its GamePad implementation, which supports both XInput and DirectInput on Windows (and for Linux/macOS, we support the standard joystick input interfaces). Even for non-XInput controllers you can expect them to map to the 360 layout provided by GamePad; SDL_GameController pulls in configurations from its own internal database as well as the Steam Big Picture Mode database to automap any known joystick to the 360 layout. Additionally, FNA checks for gamecontrollerdb.txt in the game's root folder so that developers and customers without access to the other databases may add their own configurations as well. Lastly, our implementation is far more flexible - we support numerous extensions and environment variables to allow modernized input support, including vendor/product detection, higher player counts, motion controls, etc.

+

For more information, see the SDL_GameController documentation.

+

Filesystem Portability

+

Here are three things you may get hit by when running an FNA title on Linux and macOS:

+
    +
  • Paths are separated with /, NOT with \! We do what we can to accommodate this in FNA, but you should not depend on us saving you here!
  • +
  • On Linux, filenames are case-sensitive! For example, fileName is NOT the same as Filename!
  • +
  • There are no drive letters on Unix-like operating systems. For example, while a user folder on Windows is typically C:\Users\flibitijibibo\, on Linux it's typically /home/flibitijibibo/, and on macOS it's /Users/flibitijibibo/.
  • +
+

There exists an environment variable called MONO_IOMAP that can attempt to resolve these for you, but you should not depend on this feature, as it is neither guaranteed to work nor is it performant! Your I/O performance can get hit by this!

+

The solution is to just check your code to be sure that you do not depend on any Windows-specific behavior in your code. We recommend changing your code rather than your content's filenames, as that will retain consistency across all platforms without, say, having to worry about platform content folders X and Y when rebuilding your content. It's confusing and it probably won't work if you deploy from Windows anyway.

+

The best thing you can do to make your file reading portable is to use TitleContainer.OpenStream instead of File.OpenRead, as this deals with both directory separators as well as special path requirements for platforms with unique filesystems (but this does NOT deal with case sensitivity!). Other helpful features in C# are Path.DirectorySeparatorChar and Path.Combine(), found in System.IO.

+

Environment.SpecialFolder

+

For the most part, these simply don't work correctly on Linux or macOS.

+

If you use StorageDevice for all your file I/O, don't worry - we take care of this for you.

+
    +
  • For Linux/*BSD, save directories are meant to go in the location specified by the XDG specification.
      +
    • Config files should go in $XDG_CONFIG_HOME, or ~/.config/YourApp/.
    • +
    • Save data should go in $XDG_DATA_HOME, or ~/.local/share/YourApp/.
    • +
    +
  • +
  • For macOS, save directories are meant to go in the location required by Apple for certification.
      +
    • All user data should go in ~/Library/Application Support/YourApp/.
    • +
    +
  • +
  • Other platforms may have their own specific needs for savedata.
      +
    • For unspecified platforms, consider using SDL_GetPrefPath.
    • +
    +
  • +
+

It is strongly recommended that you try to determine these locations at runtime, rather than with platform defs. You want to be able to leverage our single-assembly portability!

+

Here's an example for determining the save location at runtime:

+
using System;
+using System.IO;
+using SDL2;
+
+public const string GameName = "flibitGame";
+public static readonly string SaveDirectory = GetSaveDirectory();
+
+private static string GetSaveDirectory()
+{
+    string platform = SDL.SDL_GetPlatform();
+    if (platform.Equals("Windows"))
+    {
+        return Path.Combine(
+            Environment.GetFolderPath(
+                Environment.SpecialFolder.MyDocuments
+            ),
+            "SavedGames",
+            GameName
+        );
+    }
+    else if (platform.Equals("Mac OS X"))
+    {
+        string osConfigDir = Environment.GetEnvironmentVariable("HOME");
+        if (String.IsNullOrEmpty(osConfigDir))
+        {
+            return "."; // Oh well.
+        }
+        osConfigDir += "/Library/Application Support";
+        return Path.Combine(osConfigDir, GameName);
+    }
+    else if (   platform.Equals("Linux") ||
+            platform.Equals("FreeBSD") ||
+            platform.Equals("OpenBSD") ||
+            platform.Equals("NetBSD")   )
+    {
+        string osConfigDir = Environment.GetEnvironmentVariable("XDG_DATA_HOME");
+        if (String.IsNullOrEmpty(osConfigDir))
+        {
+            osConfigDir = Environment.GetEnvironmentVariable("HOME");
+            if (String.IsNullOrEmpty(osConfigDir))
+            {
+                return "."; // Oh well.
+            }
+            osConfigDir += "/.local/share";
+        }
+        return Path.Combine(osConfigDir, GameName);
+    }
+    else
+    {
+        return SDL.SDL_GetPrefPath(CompanyName, GameName);
+    }
+}
+
+
+

User32, Kernel32, etc.

+

Some XNA games use the Windows API directly for features such as the event loop.

+

FNA actually uses the SDL_Event loop internally, so you cannot directly replace the event loop as you would want to do with SDL_PollEvent. Instead, use SDL_AddEventWatch or SDL_SetEventFilter:

+

https://wiki.libsdl.org/SDL_AddEventWatch +https://wiki.libsdl.org/SDL_SetEventFilter

+

When SDL pumps events, these callbacks will be called with the relevant events.

+

For a more complete guide to the SDL API, see the SDL wiki:

+

https://wiki.libsdl.org/APIByCategory

+

Most (if not all) Win32 functions can be replaced with an equivalent SDL call.

+

System.Drawing

+

For the most part this is a harmless namespace, unless you make a call that depends on GDI+. System.Drawing.Imaging is particularly painful in this respect.

+

The problem is that on Linux and macOS, you will need libgdiplus, which has an insane amount of dependencies, absolutely none of which you want.

+

Sometimes you can simply replace this code with values or constants that replace the GDI-dependent values, but sometimes you may need a real replacement for things like software surface manipulation. In this case, you can use the SDL_Surface API:

+

https://wiki.libsdl.org/CategorySurface

+

System.Windows.Forms

+

In many XNA games, there is frequent use of the System.Windows.Forms namespace for various operations, mostly related to window management.

+

We strongly recommend replacing this with SDL2 when moving to FNA. The SDL2 documentation can be found here:

+

https://wiki.libsdl.org/APIByCategory

+

Subsystems like SDL_Video, SDL_Cursor, and SDL_Clipboard should be able to provide you with the same functionality found in System.Windows.Forms.

+

For example, if you want to use a messagebox:

+
#if SDL2
+    SDL2.SDL.SDL_ShowSimpleMessageBox(
+        SDL2.SDL.SDL_MessageBoxFlags.SDL_MESSAGEBOX_ERROR,
+        title,
+        message,
+        game.Window.Handle
+    );
+);
+#else
+    System.Windows.Forms.MessageBox.Show(
+        message,
+        title
+    );
+#endif
+
+

Using the FNA GameWindow in System.Windows.Forms

+

As mentioned in the previous section, you should NOT be using System.Windows.Forms when shipping an FNA game. However, if you simply want to use it for something like a developer-internal editor that's only used on Windows, there is still a way to hook the FNA window to a Form.

+

Because the GameWindow.Handle no longer directly refers to a Win32 HWND, System.Windows.Forms code that depends on this handle will no longer work. But, there is still a way to keep most of your code here.

+

For an example on how to use FNA's GameWindow.Handle IntPtr for System.Windows.Forms, see this example:

+

https://gist.github.com/flibitijibibo/cf282bfccc1eaeb47550

+

Note that you want the window to be borderless when attaching to a Panel. To do this, you can use the GameWindow.IsBorderlessEXT extension to remove the border.

+

C++/CLI Assemblies

+

C++/CLI is not available in Mono, so these binaries cannot run anywhere except on Windows with .NET.

+

The solution is to separate the C# half from the native C/C++ half, and access the native half with P/Invoke calls and native C entry points. For example:

+
/* somelib.h */
+#ifndef SOMELIB_H
+#define SOMELIB_H
+
+#include <stdint.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#ifdef _WIN32
+    #define EXPORTFN __declspec(dllexport)
+    #define DELEGATECALL __stdcall
+#else
+    #define EXPORTFN
+    #define DELEGATECALL
+#endif
+
+typedef void (DELEGATECALL *SomeCallback)(int32_t);
+
+EXPORTFN void SomeFunction(SomeCallback callback);
+
+#undef EXPORTFN
+#undef DELEGATECALL
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* SOMELIB_H */
+
+/* End somelib.h */
+
+/* somelib.c */
+#include "somelib.h"
+
+void SomeFunction(SomeCallback callback)
+{
+    callback(1337);
+}
+/* End somelib.c */
+
+/* SomeLib.cs */
+using System.Runtime.InteropServices;
+
+public static class SomeLib
+{
+    public delegate void SomeCallback(int result);
+
+    [DllImport("somelib.dll", CallingConvention = CallingConvenction.Cdecl)]
+    public static extern void SomeFunction(SomeCallback callback);
+}
+/* End SomeLib.cs */
+
+ +
+
+ +
+
+ +
+ +
+ +
+ + + + GitHub + + + + « Previous + + + Next » + + +
+ + + + + + + + + diff --git a/5:-FNA-Extensions/index.html b/5:-FNA-Extensions/index.html new file mode 100644 index 0000000..3dbf451 --- /dev/null +++ b/5:-FNA-Extensions/index.html @@ -0,0 +1,615 @@ + + + + + + + + 5: FNA Extensions - FNA Docs + + + + + + + + + + + + + + +
+ + +
+ +
+
+ +
+
+
+
+ +

5: FNA Extensions

+

FNA, in rare cases, will provide extensions to the XNA 4.0 Refresh specification in order to simplify often-unportable tasks, provide feature parity with other projects such as the MonoGame project, and so on.

+
+

GamePad Extensions

+

The GamePad extensions are unique in that there are a lot of them (our policy is generally to not extend the spec unless it's a life-or-death situation) and that they are pretty much universally recommended to use, unlike the other extensions which generally exist for very specific use cases. FNA currently has the following extensions for controller support:

+

GetGUIDEXT

+

public static string GetGUIDEXT(PlayerIndex playerIndex) is a new method for GamePad that allows you to get the hardware GUID for a given controller. Since GUIDs are organized differently depending on the OS, we have abstracted the GUID string into a shortened, unified format that is consistent across operating systems.

+

To use the GetGUIDEXT extension:

+
public MyControllerType GetControllerType(PlayerIndex index)
+{
+    /* Try to only do this once on initialization! It's slow! */
+    string guid = GamePad.GetGUIDEXT(index);
+    if (guid.Equals("4c05c405") || guid.Equals("4c05cc09"))
+    {
+        return MyControllerType.PS4;
+    }
+    if (guid.Equals("4c05e60c"))
+    {
+        return MyControllerType.PS5;
+    }
+    if (guid.Equals("7e050920") || guid.Equals("7e053003"))
+    {
+        return MyControllerType.Nintendo;
+    }
+    return MyControllerType.Xbox;
+}
+
+

SetLightBarEXT

+

public static void SetLightBarEXT(PlayerIndex playerIndex, Color color) is a new method for GamePad that allows you to set the color of the light bar on the DualShock 4 and DualSense controllers. There is a matching bool HasLightBarEXT property in GamePadCapabilities.

+

To use the SetLightBarEXT extension:

+
public void UpdateLightBar()
+{
+    if (playerIsDead)
+    {
+        GamePad.SetLightBarEXT(index, Color.Red);
+    }
+    else
+    {
+        GamePad.SetLightBarEXT(index, Color.Green);
+    }
+}
+
+

SetTriggerVibrationEXT

+

public static bool SetTriggerVibrationEXT is a new method for GamePad that allows setting the speed of motors specifically found in the triggers - this is found in the Xbox One controller, for example. There is a matching bool HasTriggerVibrationMotorsEXT property in GamePadCapabilities.

+

The function is used in exactly the same way as SetVibration, so wherever you have a SetVibration in your game you will probably want a SetTriggerVibrationEXT call as well, should you choose to support trigger-specific haptics.

+

GetGyroEXT/GetAccelerometerEXT

+

public static bool GetGyroEXT/GetAccelerometerEXT are new methods for GamePad that poll the state of a gyro in a controller, should they exist (the PS4 and Switch controllers have them, for example). There are matching bool HasGyroEXT/HasAccelerometerEXT properties in GamePadCapabilities.

+

To use the extensions:

+
public static void DoMotionControls()
+{
+    Vector3 gyro, accel;
+    GamePad.GetGyroEXT(PlayerIndex.One, out gyro);
+    GamePad.GetAccelerometerEXT(PlayerIndex.One, out accel);
+    // Have fun!
+}
+
+

Button Extensions

+

A number of bitflags have been added to the Buttons enum - they are as follows:

+
Misc1EXT =  0x00000400,
+Paddle1EXT =    0x00010000,
+Paddle2EXT =    0x00020000,
+Paddle3EXT =    0x00040000,
+Paddle4EXT =    0x00080000,
+TouchPadEXT =   0x00100000
+
+

Polling these works the same way as the other buttons, call GamePadState.IsButtonDown/IsButtonUp to check for them. Note that we did NOT add properties to the GamePadButtons struct, nor did we add a new GamePadState struct, but we did add matching properties to GamePadCapabilities.

+

Content Extensions

+

Unlike XNA, Content.Load<> can also import raw (non-XNB) assets in various file formats. The list of supported formats for each content type is as follows:

+
    +
  • +

    Effect: FXB

    +
  • +
  • +

    Song: OGG/OGA, QOA

    +
  • +
  • +

    SoundEffect: WAV

    +
  • +
  • +

    Texture2D: BMP, GIF, JPEG, PNG, TGA, TIFF, DDS, QOI

    +
  • +
  • +

    TextureCube: DDS

    +
  • +
  • +

    Video: OGG/OGV

    +
  • +
+

SurfaceFormat Extensions

+

A handful of texture formats have been added for various reasons - they are listed below:

+
    +
  • ColorBgraEXT allows the use of BGRA8 texture data, rather than SurfaceFormat.Color's RGBA8 format. This is likely only useful if you are preserving data packed by the XNA 3.1 content pipeline.
  • +
  • ColorSrgbEXT and Dxt5SrgbEXT expose sRGB colorspaces to Textures and RenderTargets, where supported.
  • +
  • Bc7EXT and Bc7SrgbEXT expose BC7 compression support, where supported.
  • +
+

FNALoggerEXT

+

public static class Microsoft.Xna.Framework.FNALoggerEXT is a new class that exposes FNA's internal logging system. Simply assign the Log functions inside to use your own logging system, if desired.

+

To use the FNALoggerEXT extension:

+
static void Main(string[] args)
+{
+    /* We recommend setting this before touching anything XNA-related! */
+    FNALoggerEXT.LogInfo = (msg) => MyLogger.Log("FNA", "INFORMATION", msg);
+    FNALoggerEXT.LogWarn = (msg) => MyLogger.Log("FNA", "WARNING", msg);
+    FNALoggerEXT.LogError = (msg) => MyLogger.Log("FNA", "ERROR", msg);
+}
+
+

IsBorderlessEXT

+

public bool IsBorderlessEXT { get; set; } is a new property for GameWindow that gives you the ability to show/hide the window border without having to perform direct interop on the Handle pointer.

+

To use the IsBorderlessEXT extension:

+
public void ApplyVideoSettings()
+{
+    graphicsDeviceManager.ApplyChanges();
+
+    /* We recommend setting this after calling ApplyChanges! */
+    game.Window.IsBorderlessEXT = wantsBorderless;
+}
+
+

SetStringMarkerEXT

+

public void SetStringMarker(string text) is a new method for GraphicsDevice that abstracts access to functions like glStringMarkerGREMEDY and D3DPERF_SetMarker. It should only be accessed in a debug context!

+

To use the SetStringMarkerEXT extension:

+
[Conditional("DEBUG")]
+public void GraphicsDebugString(string text)
+{
+    graphicsDevice.SetStringMarkerEXT(text);
+}
+
+public void DrawStuff()
+{
+    GraphicsDebugString("First set of polygons");
+    graphicsDevice.DrawIndexedPrimitives(...);
+    GraphicsDebugString("Second set of polygons");
+    graphicsDevice.DrawIndexedPrimitives(...);
+}
+
+

GetKeyFromScancodeEXT

+

public static Keys GetKeyFromScancodeEXT(Keys scancode) is a new method for Keyboard that translates a Keys value, interpreted as a scancode, into the actual Keys value based on the current keyboard layout. For example, on an AZERTY keyboard, GetKeyFromScancode(Keys.Q) will return Keys.A.

+

If FNA_KEYBOARD_USE_SCANCODES is enabled, the return value will always be the same as the input value.

+

To use the GetKeyFromScancodeEXT extension:

+
private Keys PlayerForward;
+private Keys PlayerBackward;
+private Keys PlayerStrafeLeft;
+private Keys PlayerStrafeRight;
+private Keys PlayerUse;
+public void AssignDefaultKeys()
+{
+    // Typically you might just assign a Keys value directly. Not here!
+    PlayerForward =     Keyboard.GetKeyFromScancodeEXT(Keys.W);
+    PlayerBackward =    Keyboard.GetKeyFromScancodeEXT(Keys.S);
+    PlayerStrafeLeft =  Keyboard.GetKeyFromScancodeEXT(Keys.A);
+    PlayerStrafeRight = Keyboard.GetKeyFromScancodeEXT(Keys.D);
+    PlayerUse =         Keyboard.GetKeyFromScancodeEXT(Keys.E);
+}
+
+

TextInputEXT

+

public static class Microsoft.Xna.Framework.TextInputEXT is a new class that partially abstracts text input event handling.

+

To use the TextInputEXT extension:

+
using Microsoft.Xna.Framework.Input;
+using SDL2;
+
+private void OnTextInput(char c)
+{
+    if (c == (char) 22)
+    {
+        System.Console.WriteLine("PASTED: " + SDL.SDL_GetClipboardText());
+    }
+    System.Console.WriteLine("TEXT ENTERED: " + c.ToString());
+}
+
+public void StartTextInput()
+{
+    TextInputEXT.TextInput += OnTextInput;
+    TextInputEXT.StartTextInput();
+}
+
+public void StopTextInput()
+{
+    TextInputEXT.StopTextInput();
+    TextInputEXT.TextInput -= OnTextInput;
+}
+
+

In addition to standard text input, the TextInput event can push one of a series of symbols to represent various text input actions:

+
(char) 2 - Home
+(char) 3 - End
+(char) 8 - Backspace
+(char) 9 - Tab
+(char) 13 - Enter
+(char) 127 - Delete
+(char) 22 - Control+V (Paste operator)
+
+

ClickedEXT

+

public static Action<int> ClickedEXT is a new event for Mouse that allows you to receive notifications when a mouse button is clicked. One of the main flaws of the XNA Mouse API is that you are only able to get the "current" state of the mouse buttons, but mouse button input is unique due to the various ways input events can be sent. Consider the following code:

+
// Accessible by the rest of the engine
+public bool ButtonDown { get; private set; }
+
+// Somewhere in the event loop...
+if (evt.type == MOUSEBUTTONDOWN)
+{
+    ButtonDown = true;
+}
+else if (evt.type == MOUSEBUTTONUP)
+{
+    ButtonDown = false;
+}
+
+// Somewhere in the game...
+if (Input.ButtonDown)
+{
+    // Stuff!
+}
+
+

For mouse input in particular it is surprisingly common for both a button down and a button up event to show up in a single frame, so when you poll for the "current" state, you miss out on at least one input change, leading to dropped input. This is far more noticeable when using a mouse that uses tap/touch events to send mouse buttons, rather than a physical button (for example, laptop trackpads).

+

ClickedEXT will send a button index each time a mouse button is clicked:

+
// Reset this array at the end of each frame!
+private ButtonState[] mouseClicks = new ButtonState[5];
+private void OnClicked(int button)
+{
+    mouseClicks[button] = ButtonState.Pressed;
+}
+
+

This should be combined with the standard XNA Mouse API to get a fully accurate mouse button state:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ClickedEXTGetStateFinal State
✔️✔️Clicked, Pressing
Released
✔️Clicked, Released
✔️Pressing
+

The following is a basic example of ClickedEXT combined with GetState:

+
// Using the code above...
+
+// ButtonState doesn't tell us enough, let's make our own!
+public enum InputState
+{
+    Released,
+    Pressing,
+    Clicked
+}
+public InputState LeftMouseButton { get; private set; }
+
+// GetState storage
+MouseState mPrevState, mState = new MouseState();
+
+public void UpdateInput()
+{
+    mPrevState = mState;
+    mState = Mouse.GetState();
+
+    // Easiest route is to 'or' the click with the current state
+    ButtonState leftButton = mState.LeftButton | mouseClicks[0];
+
+    if (leftButton == ButtonState.Released)
+    {
+        LeftMouseButton = InputState.Released;
+    }
+    else if (mPrevState.LeftButton == ButtonState.Released)
+    {
+        LeftMouseButton = InputState.Clicked;
+    }
+    else
+    {
+        LeftMouseButton = InputState.Pressing;
+    }
+
+    // Remember to clear your click storage each frame!
+    mouseClicks[0] = ButtonState.Released;
+}
+
+

Note that ClickedEXT can send multiple clicks for a single button if it is in fact clicked multiple times (for example, if the game hitches briefly and gives the user time to click repeatedly in one frame).

+

IsRelativeMouseModeEXT

+

public static bool IsRelativeMouseModeEXT is a new property for Mouse that allows you to change the behavior of Mouse.GetState() to get relative (rather than absolute) X/Y coordinates. As part of this, it also automatically locks the mouse to the window. This is particularly helpful for first-person games, where you would otherwise have to constantly call Mouse.SetPosition() to keep the mouse in place.

+

To use the IsRelativeMouseModeEXT extension:

+
public static bool EnableMouseCapture()
+{
+    /* PSA: Please make mouse capture an option
+     * in your settings menu if it's not required!
+     * Also, maybe disable this when !IsActive.
+     */
+    Mouse.IsRelativeMouseModeEXT = true;
+}
+
+public static void UpdateMouseInput()
+{
+    Vector2 cursorChange;
+    CurrentMouseState = Mouse.GetState();
+#if FNA
+    // Yup, that's it!
+    cursorChange = new Vector2(CurrentMouseState.X, CurrentMouseState.Y);
+#else
+    // I don't feel so good...
+    cursorChange = new Vector2(
+        CurrentMouseState.X - screenCenter.X,
+        CurrentMouseState.Y - screenCenter.Y
+    );
+    Mouse.SetPosition(screenCenter.X, screenCenter.Y); // BARF
+#endif
+}
+
+

SubmitFloatBufferEXT

+

public void SubmitFloatBufferEXT(float[] buffer) is a new method for DynamicSoundEffectInstance that allows you to directly submit float samples to the source, without having to convert to signed 16-bit PCM data. Typically this is used to directly stream float samples that are often provided by default in audio decoders.

+

This function is used exactly as the official SubmitBuffer method is used in XNA.

+

TextureDataFromStreamEXT

+

public static void TextureDataFromStreamEXT(Stream stream, out int width, out int height, out byte[] pixels) is a new method for Texture2D that functions similarly to Texture2D.FromStream(), except that it does not generate a Texture2D instance. This is a convenience function that will only decode the image data for you.

+

DDSFromStreamEXT

+

public static Texture DDSFromStreamEXT(GraphicsDevice graphicsDevice, Stream stream) is a new method for Texture2D/TextureCube that functions similarly to Texture2D.FromStream(), but is specialized for loading DDS texture files. Currently this extension supports the Dxt1, Dxt3, Dxt5, ColorBgraEXT, and Dxt5SrgbEXT formats.

+

This extension also allows loading DDS image files via the Content.Load<Texture2D/TextureCube>() methods.

+

GetFormatSizeEXT, GetBlockSizeSquaredEXT

+

Two new methods for Texture are exposed to help with allocating texture staging memory.

+

The Texture.GetBlockSizeSquaredEXT method allows querying the number of pixels in a block for compressed textures. You can use this in order to round sizes up/down to the nearest block, or to calculate how large a buffer needs to be for a compressed texture of a given size. Most compressed texture formats have a block size of 4 (and thus a BlockSizeSquared of 16), but not all do. For formats that are not compressed, this method returns 1.

+

The Texture.GetFormatSizeEXT method allows querying the number of bytes in a single block (or texel, for formats where the block size is 1). For the Color format, for example, this is 4.

+

By combining these two methods you can calculate the appropriate buffer size for any arbitrary GetData call, like so:

+
var elementSize = Texture.GetFormatSizeEXT(texture.Format);
+var blockSizeSquared = Texture.GetBlockSizeSquaredEXT(texture.Format);
+var buffer = new byte[texture.Width * texture.Height * elementSize / blockSizeSquared];
+texture.GetData(buffer);
+
+

SetDataPointerEXT

+

public void SetDataPointerEXT is a new method for Buffer and Texture objects that lets you send data directly via IntPtrs rather than arrays. This is ideal for scenarios where you know your input data is valid and want to skip all the managed validation/marshaling that happens with the normal SetData functions.

+

Be warned: When I say this skips all the validation, I mean it! This will crash if you don't know what you're doing!

+

PointListEXT

+

PointListEXT is an additional enum value for PrimitiveType that allows for point rendering.

+

While FNA supports rendering point vertex data, note that the feature is very strict - we do not expose any other aspect of point rendering that existed in XNA3 and older graphics APIs. Point sprites are always enabled, and you are expected to specify point size and manage min/max sizes either in your vertex data or vertex shader, whichever is more appropriate for your application.

+

GetRenderTargetsNoAllocEXT

+

public int GetRenderTargetsNoAllocEXT(RenderTargetBinding[] output) is a new method that acts similarly to GetRenderTargets, but allows for avoiding allocating a new array for each call:

+
RenderTargetBinding[] oldTargets = new RenderTargetBinding[0]; // Declared somewhere else, I hope...
+int oldTargetCount = currentDevice.GetRenderTargetsNoAllocEXT(null);
+Array.Resize(ref oldTargets, oldTargetCount);
+currentDevice.GetRenderTargetsNoAllocEXT(oldTargets);
+
+

SetAudioTrackEXT, SetVideoTrackEXT

+

public void SetAudioTrackEXT(int track) and public void SetVideoTrackEXT(int track) are new methods for Video that enable support for multiple tracks in a single video file. This is useful for supporting multiple languages without having to duplicate video data.

+

Note that this function may have some delay when set mid-stream, as the decoder may need to complete its current audio packet before moving to the new track. To ensure a 100% clean track, call this before calling VideoPlayer.Play()!

+

Currently, you are only allowed to change audio tracks mid-stream when the channel count and sample rate match the previous track. Changing to a track with a different wave format is unsupported unless done before playback begins.

+

Video tracks must match in width, height, and YUV format. Mixing image formats in a single file is unsupported.

+

FromUriEXT

+

This is a new method for Video that works exactly as Song does - see the Song API for details!

+

FingerIdEXT

+

public int GestureSample.FingerIdEXT and public int GestureSample.FingerId2EXT are new properties for GestureSample that allow you to determine exactly which touch finger(s) were involved in the gesture.

+

Note that FingerId2EXT is only used for Pinch and PinchComplete gestures, and it will have a value of -1 for any other gesture type.

+

The following is a basic example of using these properties:

+
// Keep track of which fingers were used for these gestures
+private int DragFinger = -1;
+private int PinchFinger1 = -1;
+private int PinchFinger2 = -1;
+
+// Somewhere later...
+
+while (TouchPanel.IsGestureAvailable)
+{
+    GestureSample gesture = TouchPanel.ReadGesture();
+    if (gesture.GestureType == GestureType.Drag)
+    {
+        DragFinger = gesture.FingerIdEXT;
+    }
+    else if (gesture.GestureType == GestureType.Pinch)
+    {
+        PinchFinger1 = gesture.FingerIdEXT;
+        PinchFinger2 = gesture.FingerId2EXT;
+    }
+}
+
+ +
+
+ +
+
+ +
+ +
+ +
+ + + + GitHub + + + + « Previous + + + Next » + + +
+ + + + + + + + + diff --git a/6:-FNA-Build-Options/index.html b/6:-FNA-Build-Options/index.html new file mode 100644 index 0000000..8d32aa0 --- /dev/null +++ b/6:-FNA-Build-Options/index.html @@ -0,0 +1,213 @@ + + + + + + + + 6: FNA Build Options - FNA Docs + + + + + + + + + + + + + + +
+ + +
+ +
+
+ +
+
+
+
+ +

6: FNA Build Options

+

FNA's implementation of certain features may be configurable at build time - that is, parts of the implementation may be temporarily modified to allow for easier debugging. To enable these options, create a file next to your .sln file called FNA.Settings.props, which will look something like this:

+
<?xml version="1.0" encoding="utf-8"?>
+<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+        <PropertyGroup>
+                <DefineConstants>VERBOSE_PIPELINECACHE;$(DefineConstants)</DefineConstants>
+        </PropertyGroup>
+</Project>
+
+

You can also use this file to sneak in other bits and pieces, such as adding SDL2_image to the build (as an aside, be sure to update FNA.dll.config with dllmaps if you actually do this):

+
<?xml version="1.0" encoding="utf-8"?>
+<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+        <ItemGroup>
+                <Compile Include="lib\SDL2-CS\src\SDL2_image.cs" />
+        </ItemGroup>
+</Project>
+
+

The supported build options are listed below!

+ +
+

VERBOSE_PIPELINECACHE

+

Affected file: src/Graphics/PipelineCache.cs

+

If you want to debug the PipelineCache to make sure it's interpreting your Effects' render state changes properly, you can enable this and get a bunch of messages logged to FNALoggerEXT.

+

CASE_SENSITIVITY_HACK

+

Affected file: src/TitleContainer.cs

+

On Linux, the file system is case sensitive. This means that unless you really focused on it, there's a good chance that your filenames are not actually accurate! The result: File/DirectoryNotFound.

+

This is a quick alternative to MONO_IOMAP=all, but the point is that you should NOT depend on either of these two things. PLEASE fix your paths!

+ +
+
+ +
+
+ +
+ +
+ +
+ + + + GitHub + + + + « Previous + + + Next » + + +
+ + + + + + + + + diff --git a/7:-FNA-Environment-Variables/index.html b/7:-FNA-Environment-Variables/index.html new file mode 100644 index 0000000..c825e4a --- /dev/null +++ b/7:-FNA-Environment-Variables/index.html @@ -0,0 +1,322 @@ + + + + + + + + 7: FNA Environment Variables - FNA Docs + + + + + + + + + + + + + + +
+ + +
+ +
+
+ +
+
+
+
+ +

7: FNA Environment Variables

+

FNA will check for certain environment variables to perform certain tasks in a non-default manner. For the most part these are meant to be used by both developers and customers, but there are also cases where we use environment variables as a means of working around a specific problem without requiring a build-time option/configuration.

+

NOTE: .NET Core intentionally broke native library compatibility with System.Environment.SetEnvironmentVariable, so for any FNA3D environment variables, we recommend using SDL_SetHint instead, even though this will have to be updated when SDL3 is released. The documentation below uses this function, if you need an example. (You are of course welcome to continue using .NET Framework and Mono instead, which does not have this issue.)

+

FNA:

+ +

FNA3D:

+ +
+

FNA_GRAPHICS_ENABLE_HIGHDPI

+

While many modern displays are capable of much higher DPIs (such as "Retina" displays on macOS and iOS), XNA does not have any mechanism for supporting high-DPI rendering.

+

Enabling this feature is as simple as setting this before creating your Game:

+
Environment.SetEnvironmentVariable("FNA_GRAPHICS_ENABLE_HIGHDPI", "1");
+
+

After calling your Game constructor, you can then check to see if high-DPI creation was successful:

+

Settings.HighDPI = Environment.GetEnvironmentVariable("FNA_GRAPHICS_ENABLE_HIGHDPI") == "1";

+

While it is possible for us to create windows with high-DPI drawing space, we cannot guarantee that it will be available at all times. Worse, we can only acquire this feature on startup, so toggling high-DPI mode will require restarting your game.

+

When this is enabled, the drawable size will take priority over the window size. So, when running at 1920x1080 in windowed mode, the drawable size will be 1920x1080, but the window size will be 960x540. This allows users to set the game resolution to the maximum resolution allowed by the OS while having the appropriate window size.

+

In fullscreen mode we will continue to run at the desktop resolution with a faux-backbuffer for non-native resolutions, but in high-DPI mode the native resolution will now be the actual native resolution rather than the simulated resolution.

+

As always, be sure that your RenderTargets and Viewports match the backbuffer size and NOT the window size!

+

Lastly, when packaging for macOS, be sure this is in your app bundle's Info.plist:

+
    <key>NSHighResolutionCapable</key>
+    <string>True</string>
+
+

This variable is accessible to users by passing /enablehighdpi:1 as a launch option.

+

FNA_GRAPHICS_JPEG_SAVE_QUALITY

+

For some reason they didn't expose a quality parameter to SaveAsJpeg, so we added one ourselves! This variable is checked each time you call the function, so feel free to customize this based on the context for each time you save an image. The value is expected to be between 1 and 100.

+

FNA_KEYBOARD_USE_SCANCODES

+

XNA keys are based on keycodes, rather than scancodes.

+

With SDL2, for example, you can actually pick between SDL_Keycode and SDL_Scancode, but scancodes will not be accurate to XNA4. The benefit is that scancodes will ignore "foreign" keyboard layouts, making default keyboard layouts work out of the box everywhere (unless the actual symbol for the keys matters in your game).

+

While FNA provides the GetKeyFromScancodeEXT extension and developers are encouraged to use it, this is not a required function and users may benefit from this environment variable in the event that layouts are not checked in-game.

+

In either case, TextInputEXT will still read the actual chars correctly, so you can (mostly) have your cake and eat it too if you don't care about your bindings menu not making a lot of sense on foreign layouts.

+

To use scancodes instead of keycodes, set this variable to "1" before starting the game.

+

This variable is accessible to users by passing /usescancodes:1 as a launch option.

+

FNA_GAMEPAD_NUM_GAMEPADS

+

XNA4 supports four controllers, per XInput's limitations. However, SDL2 gives us the ability to support more controllers when available. You can set this environment variable on/before program startup to set a controller count without modifying FNA:

+
Environment.SetEnvironmentVariable("FNA_GAMEPAD_NUM_GAMEPADS", "8");
+
+

FNA_SDL2_FORCE_BASE_PATH

+

For the desktop, we use AppDomain.CurrentDomain.BaseDirectory as the root path, as this is more accurate on platforms where the "real" EXE path is the path of the CLR. However, there may be some scenarios even on desktop where SDL_GetBasePath() is more appropriate. If you want to override our defaults, this variable can be set on startup.

+

FNA3D_FORCE_DRIVER

+

FNA3D supports multiple graphics backends with a single binary. Sometimes the default may not produce the optimal result for specific hardware, or a backend may exhibit graphics driver bugs that are not present in other backends. The list of available driver strings for this environment variable is as follows, in order of the current default priority:

+
    +
  • D3D11
  • +
  • OpenGL
  • +
  • Vulkan
  • +
+

This variable is accessible to users by passing /gldevice:%s as a launch option, where %s can be one of the above strings. Note that the string must match an available driver exactly, or device creation will fail!

+

FNA3D_ENABLE_HDR_COLORSPACE

+

The XNA specification includes multiple surface formats capable of using an HDR colorspace - however, all rendering is assumed to be standard sRGB. Applications can set this variable to "1" to enable support for creating swapchains with the HDR10 (Rgba1010102) and extended sRGB (HalfVector4/HdrBlendable) colorspaces.

+

Note that this only exposes the ability to set the colorspace of the swapchain and nothing else - any and all colorspace conversion still has to be done by the application. Additionally, be careful when adding user-facing support for HDR; it should be treated as the equivalent of a full kernel modeset, so it is very expensive and fragile to attempt anywhere except on startup!

+

This feature is only supported on Vulkan and D3D11.

+

FNA3D_ENABLE_LATESWAPTEAR

+

For many years FNA would default to using FIFO_RELAXED/EXT_swap_control_tear VSync to improve performance when games temporarily performed below the display's refresh rate. However, in recent years this has fallen out of fashion; most platforms don't support this feature at all and the platforms that did support it have opted to avoid presentation systems that involve tearing at all (in favor of other systems like "mailbox" presentation). Additionally, refresh rates have rapidly become unstandardized within the last hardware generation, so fixed-rate games would see tearing if the game rate was below the refresh rate of the monitor. Some may still want to try using the late swap tear feature with their OS/driver/display combo, however, so users can set this to "1" if they'd like.

+

This variable is also accessible to users by passing /enablelateswaptear:1 as a launch option.

+

FNA3D_MOJOSHADER_PROFILE

+

FNA3D uses MojoShader to support XNA4 (D3D9) Effect shader binaries. One major benefit of MojoShader is that it supports multiple shader profiles, including GLSL and SPIR-V. By default, each renderer picks the most appropriate shader profile by itself, but you can set this variable to forcibly use a specific profile.

+

Unless you're a developer working on a new shader profile, this is probably useless to you.

+

This variable is accessible to users by passing /mojoshaderprofile:%s as a launch option, where %s can be glsl120 or glspirv. Currently this is only useful for OpenGL.

+

FNA3D_BACKBUFFER_SCALE_NEAREST

+

When using the faux-backbuffer, we use a linear filter to blit to the window's backbuffer. However, some games may prefer to use a point/nearest-neighbor filter to scale in specific scenarios (such as pixel art games running in multiples of the desktop resolution). To use that filter instead, set this variable to "1" before running the game.

+

This variable is accessible to users by passing /backbufferscalenearest:1 as a launch option.

+

FNA3D_OPENGL_WINDOW_DEPTHSTENCILFORMAT

+

OpenGL contexts are very clunky and require the RGB/Depth/Stencil sizes at window creation time rather than context creation time, and cannot be reset without destroying the window and GL context. By default we play it safe and create a window with a Depth24Stencil8 backbuffer, but if you want to optimize on this you can set this environment variable to a DepthFormat enum value at program startup to override our settings. For example:

+
SDL2.SDL.SDL_SetHintWithPriority(
+    "FNA3D_OPENGL_WINDOW_DEPTHSTENCILFORMAT",
+    "None",
+    SDL2.SDL.SDL_HintPriority.SDL_HINT_OVERRIDE
+);
+
+

Note, however, that the OS may completely ignore your settings and pick whatever it wants for its window backbuffer format. (This is rare, but it's still a nonzero chance...)

+

FNA3D_OPENGL_FORCE_ES3

+

FNA3D has support for OpenGL ES 3.0. Formerly used for iOS/tvOS, it is also known to work with Google's ANGLE project (D3D11 only) and, to some extent, some Android hardware. This may also be useful to those that would like to try to port to new ES platforms (WebAssembly, Android, etc.). Simply set this to "1" on startup and an ES context will be used instead of a desktop context.

+

This variable is accessible to users by passing /glprofile:es3 as a launch option.

+

FNA3D_OPENGL_FORCE_CORE_PROFILE

+

FNA3D currently aims for OpenGL 2.1 with a handful of ARB extensions for features like multisampling, hardware instancing, and so on. However, we are also capable of targeting the OpenGL 4.6 Core Profile, with a small handful of mostly-harmless changes. RenderDoc requires this to capture frames; set this variable to "1" in the capture GUI before launching to successfully capture frames.

+

While this does enable Core Profile features, note that we still do what your engine tells us to do. For example, even though client-side vertex/index data is forbidden, we still still attempt to render as such, and ARB_debug_output as well as the RenderDoc capturing system will throw errors (and possibly crash) because you are not using buffer objects.

+

Note that this is probably useless in every situation other than accommodating graphics debuggers.

+

This variable is accessible to users by passing /glprofile:core as a launch option.

+

FNA3D_OPENGL_FORCE_COMPATIBILITY_PROFILE

+

Some devices (i.e. NVIDIA Tegra) will default to either an OpenGL ES context or a modern OpenGL context that is incompatible with certain legacy OpenGL features. However, sometimes those devices will support the context version that FNA3D tries to aim for, so this variable exists to let developers attempt a 2.1 Compatibility context for those situations. That said, if your device wants a context version that your data isn't compatible with, try fixing the data at some point!

+

This variable is accessible to users by passing /glprofile:compatibility as a launch option.

+

FNA3D_VULKAN_FORCE_MAILBOX_VSYNC

+

"Mailbox" vsync is effectively triple-buffer vsync - no tearing is present, but presentation does not wait for vblank, it simply stages the finished frame and continues immediately, allowing for high framerates and low latency without having to trade off a constant tear line on the screen. This has a very narrow use case and is not recommended unless you absolutely know what you're doing!

+

This variable is accessible to users by passing /forcemailboxvsync:1 as a launch option.

+

FNA3D_VULKAN_PIPELINE_CACHE_FILE_NAME

+

To improve loading performance, FNA3D saves Vulkan pipeline objects to a file. This cache is hardware- and driver-specific, so expect a recompile any time the graphics hardware changes in any way.

+

For debug builds we save to the game folder, and for release builds we save to a separate FNA3D/ folder where app data is traditionally stored. You can set this to any path you want as long as you set it as soon as the program starts.

+

FNA3D_VULKAN_DEVICE_LOCAL_HEAP_USAGE_FACTOR

+

Vulkan puts 100% of the memory management responsibilities on the application, rather than the driver. Because of this, we also have to manage memory from all of its possible locations, including memory on the "host" (CPU memory) and memory that is "device-local" (GPU memory). This distinction has a dramatic effect on the behavior of games, particularly the variations in available device-local memory, so this variable exists to emulate low-VRAM situations for debugging possible issues with the memory allocator and defragmenter.

+

This value can be set between 0.0 and 1.0 (exclusive), representing the percentage of VRAM that you want to restrict the FNA3D device to. Note that this is very much a developer-only tool, do not use this unless you're trying to diagnose Vulkan memory allocation bugs!

+ +
+
+ +
+
+ +
+ +
+ +
+ + + + GitHub + + + + « Previous + + + Next » + + +
+ + + + + + + + + diff --git a/8:-Contributing-to-FNA/index.html b/8:-Contributing-to-FNA/index.html new file mode 100644 index 0000000..ad02168 --- /dev/null +++ b/8:-Contributing-to-FNA/index.html @@ -0,0 +1,218 @@ + + + + + + + + 8: Contributing to FNA - FNA Docs + + + + + + + + + + + + + + +
+ + +
+ +
+
+ +
+
+
+
+ +

8: Contributing to FNA

+

This is a rough guide for contributing to FNA. This should NOT be considered the be-all-end-all rulebook for FNA development, but it contains some basic examples of how the project is written and developed. In general, Rule #0 is simply to use your best judgement based on existing traditions in the code and its libraries.

+

Forks

+

GitHub makes forking as easy as clicking a button. Here's when to click that button:

+
    +
  • When you're about to commit a patch
  • +
+

Do NOT clutter up the network with pointless forks! Either write something or just use our repository. We promise that the clone that you have locally on your machine will never be forcibly updated by us unless you explicitly type git pull (that's how Git works!), and we will always have stable releases archived. The network graph is critical for tracking developers' changes, if too many forks exist then the feature is forcibly turned off by GitHub!

+

Code Style

+
    +
  • Line Endings: Unix newlines ('\n'), NOT Win32 newlines ('\r\n')!
  • +
  • Tabs: Actual '\t' tabs, NOT SPACES!
  • +
  • Blank Lines: NO TRAILING SPACE! Blank lines should be, you know, blank.
  • +
  • Characters per line: ~100. When a line gets too long, start splitting it up into multiple lines. The number should only be considered a maximum value; if you find that you're doing a lot of extensive left-to-right reading rather than top-to-bottom, start splitting the lines up.
  • +
  • i++/i--: Do i += 1 and i -= 1 instead unless the increment is genuinely being used to its advantage. If it's that painful to do this, consider foreach instead.
  • +
  • Do NOT use var! Use the actual type name!
  • +
  • someMethod() rather than someMethod (), someArray[x] rather than someArray [x], etc.
  • +
  • (Type) cast rather than (Type)cast.
  • +
  • Use braces everywhere! I don't care if the if block is one line, braces! Use them!
  • +
  • Single line comments: // This code is derp rather than //this code is derp.
  • +
  • Multi-line comments: Use /* */ blocks, rather than multiple lines of //.
  • +
+

External Libraries

+

As noted in the download documentation, we use several third-party libraries to support FNA.

+

A major rule for FNA is that we do NOT fork external libraries! Anything we change must be submitted to upstream, and must be of high enough quality to be merged. FNA's role is solely to reimplement XNA, nothing further. Any issue exhibited by the libraries must be fixed in the library for the benefit of the greater software ecosystem.

+

The sources can be found here:

+ +

All libraries are built with the default settings.

+ +
+
+ +
+
+ +
+ +
+ +
+ + + + GitHub + + + + « Previous + + + Next » + + +
+ + + + + + + + + diff --git a/appendix/Appendix-A:-FNA-on-iOS-and-tvOS/index.html b/appendix/Appendix-A:-FNA-on-iOS-and-tvOS/index.html new file mode 100644 index 0000000..2a2febb --- /dev/null +++ b/appendix/Appendix-A:-FNA-on-iOS-and-tvOS/index.html @@ -0,0 +1,409 @@ + + + + + + + + Appendix A: FNA on iOS and tvOS - FNA Docs + + + + + + + + + + + + + + +
+ + +
+ +
+
+ +
+
+
+
+ +

Appendix A: FNA on iOS and tvOS

+

As of FNA 24.03, FNA supports deploying to iOS and tvOS via the .NET SDK. FNA does not have special branches or configurations for each platform; the public master branch of FNA and the configuration you're already used to is exactly what is used to ship for these targets. The platform code is contained entirely in SDL.

+

Getting Started

+

This is the basic guide to getting your game running in iOS/tvOS. Once these steps are followed, your game should be able to boot on real hardware.

+

Prerequisites

+

In order to build and deploy FNA apps for iOS/tvOS, you must have the following:

+
    +
  1. Mac hardware (required by Apple)
  2. +
  3. The latest .NET SDK for macOS
  4. +
  5. The latest version of Xcode, downloaded from the macOS App Store or from the Apple Developer site
  6. +
  7. The iOS/tvOS workloads for the .NET SDK. Once you have installed .NET on your Mac, run the following commands:
  8. +
+
sudo dotnet workload install ios
+sudo dotnet workload install tvos
+
+

Building fnalibs

+

The process of building all the fnalibs is normally very tedious, and everybody loves how good Xcode is, so as an alternative to building each project by hand, we have a pair of build scripts that automatically download and build all of the fnalibs at once. If you would like to see the process in detail, take a look at the script sources - they're human-readable!

+

Creating an iOS Project

+

Creating a .NET iOS project is almost identical to the process of creating a desktop .NET project. +The only difference is that instead of dotnet new console, you must run dotnet new ios. +This will generate an "iOS Application" template project we can then modify like so:

+
    +
  1. Delete the SceneDelegate.cs and AppDelegate.cs files
  2. +
  3. Update the Info.plist file as follows:
  4. +
  5. Set your application metadata (display name, bundle identifier, etc.)
  6. +
  7. Specify which device orientations you want to support (landscape, portrait, etc.)
  8. +
  9. Change the UIRequiredDeviceCapabilities value from armv7 to arm64
  10. +
  11. If your game supports game controllers (MFi, Xbox, PlayStation, etc.) add the following:
  12. +
+
   <key>GCSupportedGameControllers</key>  
+   <array>
+       <dict>
+           <key>ProfileName</key>
+           <string>ExtendedGamepad</string>
+       </dict>
+   </array>  
+   <key>GCSupportsControllerUserInteraction</key>  
+   <true/>
+
+

See this Apple documentation for more information about how to support different kinds of controllers.

+
    +
  1. Add the following to your game's .csproj:
  2. +
+
<PropertyGroup>
+  <MtouchNoSymbolStrip>true</MtouchNoSymbolStrip>
+  <MtouchExtraArgs>--cxx --gcc_flags "-L$(MSBuildProjectDirectory) -force_load $(MSBuildProjectDirectory)/libSDL2.a -force_load $(MSBuildProjectDirectory)/libFAudio.a -force_load $(MSBuildProjectDirectory)/libFNA3D.a -force_load $(MSBuildProjectDirectory)/libtheorafile.a -force_load $(MSBuildProjectDirectory)/libMoltenVK.a -framework AudioToolbox -framework AVFoundation -framework AVFAudio -framework CoreBluetooth -framework CoreHaptics -framework GameController -framework OpenGLES -framework QuartzCore -framework Metal -framework CoreMotion -framework CoreGraphics -framework IOSurface"</MtouchExtraArgs>
+  <_ExportSymbolsExplicitly>false</_ExportSymbolsExplicitly>
+</PropertyGroup>
+
+
    +
  1. Place all the native libraries in your project directory. They will be linked on build, as specified by the MtouchExtraArgs property you just added to the project.
  2. +
  3. Add a project reference to FNA.Core.csproj, as per usual. And that's it for the project setup!
  4. +
+

Creating a tvOS Project

+

Creating a .NET tvOS project is almost identical to the process of creating a desktop .NET project. +The only difference is that instead of dotnet new console, you must run dotnet new tvos. +This will generate a "tvOS Application" template project we can then modify like so:

+
    +
  1. Delete the ViewController.cs, ViewController.designer.cs, and AppDelegate.cs files
  2. +
  3. Rename Main.storyboard to LaunchScreen.storyboard and delete the following from line 10 of the file: +customClass="ViewController"
  4. +
  5. Update the Info.plist file as follows:
  6. +
  7. Set your application metadata (display name, bundle identifier, etc.)
  8. +
  9. If your game supports game controllers (MFi, Xbox, PlayStation, etc.) add the following:
  10. +
+
   <key>GCSupportedGameControllers</key>  
+   <array>
+       <dict>
+           <key>ProfileName</key>
+           <string>ExtendedGamepad</string>
+       </dict>
+   </array>  
+   <key>GCSupportsControllerUserInteraction</key>  
+   <true/>
+
+

See this Apple documentation for more information about how to support different kinds of controllers. + - Point the app to the correct storyboard file, like so:

+
    <!-- Replace this... -->
+    <key>UIMainStoryboardFile</key>
+    <string>Main</string>
+
+    <!-- ...with this! -->
+    <key>UILaunchStoryboardName</key>
+    <string>LaunchScreen</string>
+
+
    +
  1. Add the following to your game's .csproj:
  2. +
+
<PropertyGroup>
+  <MtouchNoSymbolStrip>true</MtouchNoSymbolStrip>
+  <MtouchExtraArgs>--cxx --gcc_flags "-L$(MSBuildProjectDirectory) -force_load $(MSBuildProjectDirectory)/libSDL2.a -force_load $(MSBuildProjectDirectory)/libFAudio.a -force_load $(MSBuildProjectDirectory)/libFNA3D.a -force_load $(MSBuildProjectDirectory)/libtheorafile.a -force_load $(MSBuildProjectDirectory)/libMoltenVK.a $(MSBuildProjectDirectory)/libtvStubs.a -framework AVFoundation -framework AudioToolbox -framework CoreGraphics -framework Metal -framework QuartzCore -framework OpenGLES -framework GameController -framework CoreBluetooth -framework MobileCoreServices -framework ImageIO -framework IOSurface -framework CoreHaptics"</MtouchExtraArgs>
+  <_ExportSymbolsExplicitly>false</_ExportSymbolsExplicitly>
+</PropertyGroup>
+
+
    +
  1. Place all the native libraries in your project directory. They will be linked on build, as specified by the MtouchExtraArgs property you just added to the project.
  2. +
  3. Add a project reference to FNA.Core.csproj, as per usual. And that's it for the project setup!
  4. +
+

Code Changes

+

Initialization

+

To get your game booting on iOS/tvOS, update your Main.cs as follows:

+
    static void Main(string[] args)
+#if __IOS__ || __TVOS__
+    {
+        // Enable high DPI "Retina" support. Trust us, you'll want this.
+        SDL2.SDL.SDL_SetHint("FNA_GRAPHICS_ENABLE_HIGHDPI", "1");
+
+        // Keep mouse and touch input separate.
+        SDL2.SDL.SDL_SetHint(SDL2.SDL.SDL_HINT_MOUSE_TOUCH_EVENTS, "0");
+        SDL2.SDL.SDL_SetHint(SDL2.SDL.SDL_HINT_TOUCH_MOUSE_EVENTS, "0");
+
+        // Don't let the accelerometer take over controller slot 0.
+        SDL2.SDL.SDL_SetHint(SDL2.SDL.SDL_HINT_ACCELEROMETER_AS_JOYSTICK, "0");
+
+        realArgs = args;
+        SDL2.SDL.SDL_UIKitRunApp(0, IntPtr.Zero, FakeMain);
+    }
+
+    static string[] realArgs;
+
+    [ObjCRuntime.MonoPInvokeCallback(typeof(SDL2.SDL.SDL_main_func))]
+    static int FakeMain(int argc, IntPtr argv)
+    {
+        RealMain(realArgs);
+        return 0;
+    }
+
+    static void RealMain(string[] args)
+#endif
+    {
+        // your stuff
+    }
+
+

Background Safety

+

To avoid crashes when your app enters into a background state, add the following to your Game.Update method:

+
if (!IsActive)
+{
+    SuppressDraw();
+}
+
+

Retina Displays

+

For your app to take full advantage of iPhone and iPad retina displays, you need to set your preferred backbuffer size to the full extent of the display. This should happen at the start of your game and in the Window.OrientationChanged event callback (if your app supports both portrait and landscape orientations).

+
graphics.PreferredBackBufferWidth = GraphicsAdapter.DefaultAdapter.CurrentDisplayMode.Width;
+graphics.PreferredBackBufferHeight = GraphicsAdapter.DefaultAdapter.CurrentDisplayMode.Height;
+
+

tvOS User Storage

+

Note that tvOS does NOT have local storage available. There is temporary storage but it can be wiped by the OS at any time, so standard filesystem code will NOT work past a single instance of the program (this unfortunately includes Microsoft.Xna.Framework.Storage). To support save data you will need to implement some form of iCloud support, but on the bright side, this means iOS/tvOS can share that data. Our recommended solution for games with a guaranteed total save data size of <1MB is to use TinyKVS.

+

If you're using iCloud and receive an error about iCloudContainerEnvironment when submitting to the App Store, add this to your Entitlements.plist for that submission:

+
<key>com.apple.developer.icloud-container-environment</key>
+<array>
+  <string>Production</string>
+</array>
+
+

Content Changes

+

All your game assets (e.g. your Content/ folder) must be placed inside the Resources/ folder in your project directory. (On tvOS you will need to add this folder manually.) This will ensure they are copied into the app bundle.

+

If you load any resources yourself via a FileStream, the stream must have a file access parameter of FileAccess.Read or you'll get a System.UnauthorizedAccessException.

+

Texture Compression

+

iOS/tvOS do NOT support DXT compression, so you will want to build uncompressed textures at compile time to avoid our fallback decompressor, which wastes lots of time and memory. We are open to considering other texture formats (namely, those supported by Basis) once we have active use cases to look at.

+

Running Your App

+

Deploying your app to a simulator or physical device can be a bit tricky, but thankfully the .NET documentation can walk you through the process. Please see this page for instructions, but skip to step 5 of the introduction, as the preceding steps are only applicable to MAUI apps.

+

As you might expect, you can substitute ios for tvos in the build line when running a tvOS app.

+

Note that to run your app in the iOS/tvOS simulators on an Intel-based Mac, you must include x86_64 builds of your native libraries. For the fnalibs, these are automatically generated by the build scripts when run with "ios-sim" or "tvos-sim".

+

Debugging

+

Debugging an FNA game running on a physical device is not yet supported due to bugs with Visual Studio Code's MAUI extension.

+

Profiling

+

See this wiki page from the official repository for instructions on how to profile your app.

+

Troubleshooting

+
    +
  • +

    If you get a bunch of this spam in your terminal during the build process: Requested but did not find extension point with identifier ... for extension ... don't be alarmed. These messages, while annoying, are harmless and can be ignored.

    +
  • +
  • +

    If you don't see any Console.WriteLine messages or exception logs in the Terminal, try checking the Console app on macOS. Select your iOS/tvOS device from the menu on the left, then add your app's name as a Process filter in the top-right. This should show you all the logs relevant to your app. (We're fairly certain the lack of messages is a .NET tooling bug, so hopefully this will be resolved in the future.)

    +
  • +
  • +

    If .NET whines that it "Could not find any provisioning profiles for iOS", you need to create a dummy Xcode iOS project with the same bundle identifier, which will create a provisioning profile. Then you shouldn't need to touch the Xcode project ever again.

    +
  • +
  • +

    If you're not getting any sound, flip the ringer switch on the side of the phone to be unmuted. When you set your phone to silent, it mutes all sound from games, unlike other app types (such as media players).

    +
  • +
  • +

    If you get a DllNotFoundException from a native library at runtime, make sure to include the native library in the MtouchExtraArgs property, alongside the fnalibs, like so: -force_load $(MSBuildProjectDirectory)/{your_library}.a

    +
  • +
  • +

    If a tvOS build fails because the linker complains about an "undefined symbol", that could mean one of two things...

    +
      +
    1. You forgot to add a native library to the MtouchExtraArgs property, as described earlier.
    2. +
    3. There is an extern method somewhere in your code (or in the fnalibs) that refers to a function not defined for the tvOS platform. If it's a problem with one of the fnalibs, file an issue and we can take a look. If it's in your code, consider removing the extern declaration or wrapping it inside #if !__TVOS__.
    4. +
    +
  • +
+ +
+
+ +
+
+ +
+ +
+ +
+ + + + GitHub + + + + « Previous + + + Next » + + +
+ + + + + + + + + diff --git a/appendix/Appendix-B:-FNA-on-Consoles/index.html b/appendix/Appendix-B:-FNA-on-Consoles/index.html new file mode 100644 index 0000000..73828f8 --- /dev/null +++ b/appendix/Appendix-B:-FNA-on-Consoles/index.html @@ -0,0 +1,297 @@ + + + + + + + + Appendix B: FNA on Consoles - FNA Docs + + + + + + + + + + + + + + +
+ + +
+ +
+
+ +
+
+
+
+ +

Appendix B: FNA on Consoles

+

FNA supports deploying to Xbox and Nintendo Switch via NativeAOT. FNA does not have any private branches for each platform; the public master branch of FNA is exactly what is used to ship for these targets. The platform code is contained entirely in SDL and the NativeAOT bootstrap.

+
+

General Advice

+

For all console builds, you should make .NET 8 project files for your game - instead of the usual FNA.csproj, you will reference FNA.Core.csproj. The code and content should largely be able to stay the same, with the exception of code that requires a JIT (i.e. you can't emit IL at runtime, as you might expect from ahead-of-time compilation).

+

While the runtimes require a console NDA, there are some things you can do to make your game more robust that just-so-happens to make console support easier, without access to any particular SDK. If you're familiar with consoles, none of these will be surprising:

+

Window Size Changes

+

Even if your window is not resizable, operating systems (including Windows!) may forcibly change the window size for a multitude of reasons, and so the graphics device will reset.

+

Consider the following code:

+
void ApplyVideoSettings(int width, int height, bool fullscreen, bool vsync);
+{
+    // Update GraphicsDeviceManager...
+    graphics.PreferredBackBufferWidth = width;
+    graphics.PreferredBackBufferHeight = height;
+    graphics.SynchronizeWithVerticalRetrace = vsync;
+    graphics.IsFullScreen = fullscreen;
+
+    // Apply!
+    graphics.ApplyChanges();
+
+    // A bunch of engine stuff
+    menu.ResizeScreen();
+    renderer.RecreateRenderTargets();
+}
+
+

This is actually kind of wrong; even in fullscreen mode it is possible for the operating system to affect your window size and break your game. But you don't need to go through a bunch of trouble to support AllowUserResizing or anything like that, as XNA internally hooks up ClientSizeChanged to GraphicsDeviceManager.ApplyChanges(), which should lead to a reset that you can catch with GraphicsDeviceManager.DeviceReset events:

+
public MyGame() : base()
+{
+    graphics = new GraphicsDeviceManager(this);
+    graphics.DeviceCreated += OnDeviceCreated;
+    graphics.DeviceReset += OnDeviceReset;
+}
+
+private void OnDeviceCreated(object sender, EventArgs e)
+{
+    // A bunch of engine size stuff before Initialize()
+}
+
+private void OnDeviceReset(object sender, EventArgs e)
+{
+    // A bunch of engine stuff
+    menu.ResizeScreen();
+    renderer.RecreateRenderTargets();
+}
+
+void ApplyVideoSettings(int width, int height, bool fullscreen, bool vsync);
+{
+    // Update GraphicsDeviceManager...
+    graphics.PreferredBackBufferWidth = width;
+    graphics.PreferredBackBufferHeight = height;
+    graphics.SynchronizeWithVerticalRetrace = vsync;
+    graphics.IsFullScreen = fullscreen;
+
+    // Apply!
+    graphics.ApplyChanges();
+
+    // A bunch of engine stuff used to be here. It's gone now.
+}
+
+

Even if you do not support resizable windows, you need to be prepared for resizes to happen at absolutely any time in your program.

+

Filesystem Portability (Again)

+

Unless you're working with savedata, using System.IO.File is highly discouraged. (And even then, savedata was supposed to be done with Microsoft.Xna.Framework.Storage in the XBLIG world). If you're loading files with File.Open specifically, your game may not even work on PC because the player may have the game installed in a location without write permissions!

+

To load files, use TitleContainer.OpenStream instead. Save data should be handled with Microsoft.Xna.Framework.Storage, but if you already have established savedata out in the wild, isolate your filesystem calls as much as possible. Lord knows how many times I've done this to make Linux savedata not go directly in $HOME...

+

Xbox GDK

+

GDK support is now available to ID@Xbox licensees. SDL supports GDK on both PC and Xbox, and FAudio/Theorafile work as-is. FNA3D is currently targeting GDKX via a port of GLon12, which we upstreamed for release in Mesa 23.1.

+

Developers can request NativeAOT-GDKX access via Discord once they have signed the GDK agreements with Microsoft.

+

Building fnalibs

+

SDL, FNA3D, FAudio, and Theorafile all have VisualC-GDK folders with pre-made project files. Compile, grab the DLLs, add said DLLs to your project.

+

Code Differences

+

Your code should be able to stay the same except for the Main function:

+
    [STAThread]
+    static void Main(string[] args)
+#if GDK
+    {
+        realArgs = args;
+        SDL2.SDL.SDL_main_func mainFunction = FakeMain;
+        SDL2.SDL.SDL_GDKRunApp(mainFunction, IntPtr.Zero);
+    }
+
+    static string[] realArgs;
+    static int FakeMain(int argc, IntPtr argv)
+    {
+        RealMain(realArgs);
+        return 0;
+    }
+
+    static void RealMain(string[] args)
+#endif
+    {
+        // blah blah blah
+    }
+
+

Nintendo Switch

+

While there is no special code needed for Nintendo Switch support (100% of the platform code is in SDL and NativeAOT, two separate projects), all consulting and documentation is private, per NDA requirements. If you are a licensed developer, please get access to SDL-switch (search the licensee forums) and then get in touch with flibit on Discord. If you are NOT a licensed developer, you're on your own. None of us are able to get you hooked up, so please only get in touch AFTER you have Switch SDK access.

+

PlayStation

+

FNA for PlayStation 4 and 5 is now in progress - the first draft of SDL-playstation was recently finished, with FNA3D support coming up next. FAudio and Theorafile are already working on PlayStation targets! For runtimes we are currently using NativeAOT, with Mono as our fallback plan.

+

If you are a licensee, please get in touch with Ryan for SDL access, then once you have access to that, let us know!

+ +
+
+ +
+
+ +
+ +
+ +
+ + + + GitHub + + + + « Previous + + + Next » + + +
+ + + + + + + + + diff --git a/appendix/Appendix-C:-FNA-on-Google-Stadia/index.html b/appendix/Appendix-C:-FNA-on-Google-Stadia/index.html new file mode 100644 index 0000000..2603c1b --- /dev/null +++ b/appendix/Appendix-C:-FNA-on-Google-Stadia/index.html @@ -0,0 +1,196 @@ + + + + + + + + Appendix C: FNA on Google Stadia - FNA Docs + + + + + + + + + + + + + + +
+ + +
+ +
+
+ +
+
+
+
+ +

Appendix C: FNA on Google Stadia

+

UPDATE (September 29, 2022): Google has announced that they are transitioning Stadia to a white-label product, so this platform's support is now only for licensees of the Stadia technology. Stadia as a standalone platform will be discontinued in 2023.

+
+

As of FNA 19.12, if you can make a Linux version, you can make a Stadia version. No, really! FNA for Stadia, like all the other platforms, uses SDL for all platform code; the master branch you see on GitHub is exactly what ships on Stadia. In fact, aside from SDL, all of the fnalibs binaries are byte-for-byte identical between Linux and Stadia.

+

Graphics Support

+

FNA uses ANGLE's Vulkan renderer for graphics support on Stadia. This means that you will want your graphics data to be compliant with OpenGL ES 3.0 for the best result (for example, don't use ColorBgraEXT). When our Vulkan renderer is made the default this will no longer be necessary.

+

Filesystem Portability (Again)

+

As usual, be sure your loading/saving code is portable, as described earlier in this documentation!

+

Anything Else?

+

Aside from Stadia being Linux with Vulkan, PulseAudio, glibc, and libc++, there's not much else that can be discussed here. If you are a licensed Stadia developer, please get in touch with the admins in the FNA Discord server.

+ +
+
+ +
+
+ +
+ +
+ +
+ + + + GitHub + + + + « Previous + + + Next » + + +
+ + + + + + + + + diff --git a/appendix/Appendix-D:-MonoGame/index.html b/appendix/Appendix-D:-MonoGame/index.html new file mode 100644 index 0000000..79bad66 --- /dev/null +++ b/appendix/Appendix-D:-MonoGame/index.html @@ -0,0 +1,328 @@ + + + + + + + + Appendix D: MonoGame Compatibility - FNA Docs + + + + + + + + + + + + + + +
+ + +
+ +
+
+ +
+
+
+
+ +

Appendix D: MonoGame Compatibility

+

FNA is designed to be a fully-compatible XNA 4.0 Refresh reimplementation that focuses on accuracy and preservation of the XNA catalog.

+

MonoGame, in contrast, is an XNA-like framework that is designed to be like XNA, but not 100% the same. XNA compatibility is likely, but not guaranteed.

+

FNA and MonoGame are mostly compatible with one another, but there are a handful of exceptions that are documented on this page.

+
+

Code Compatibility

+

API Compatibility

+

MonoGame is mostly XNA4-compatible, but this is not a guarantee. The API is allowed to change mostly at the whims of whoever sends patches and/or has write access to the project. For a successor this makes sense because it is XNA-like, meaning it's meant to feel a lot like XNA without preservation taking priority over what the MonoGame community actually wants.

+

In the context of FNA compatibility, this can be problematic if incompatible APIs are accidentally used. MonoGame API changes are not explicitly marked or documented, so the only way to be sure you're compatible is to constantly build against XNA or FNA.

+

While FNA is extremely strict about API compatibility, we do have formal processes for adding features in the form of extensions, build options, and environment variables, all of which are very clearly marked and are explicitly mentioned in every release that affects them. These features are designed to fill in the absolute worst gaps in the XNA API (mouse events, borderless windows, etc.), and oftentimes this improves MonoGame compatibility. That said, all of these additions are subject to modification and removal, based on the needs of the ecosystem.

+

Project Compatibility

+

MonoGame has a build system that generates project files for each platform, as every platform has a special build with different backend files and platform definitions. Some platforms share project files (DesktopGL for Windows/macOS/Linux, for example), but some platforms have multiple projects (WindowsDX vs. DesktopGL, for example).

+

FNA has only one project file for all platforms and backends, and we do not use platform definitions for any reason and will ALWAYS ban them from the source code, with absolutely zero exceptions. Given that C# is managed code, we have opted to detect platform information at runtime instead, for the few scenarios that platform checks are needed. You are encouraged to do the same in your own code, to allow for single-assembly portability. "Write Once Run Anywhere" is usually a fat lie, but "Build Once Run Anywhere" is entirely feasible for managed assemblies.

+

Content Compatibility

+

FNA and MonoGame are both compatible with almost all content built by the XNA Content Pipeline. FNA's exceptions are explicitly documented.

+

Content Pipeline

+

MonoGame has their own Content Pipeline tool, called MGCB. It is meant to act as a successor to the XNA Content Pipeline and generates separate content for every platform.

+

FNA does not have and will not have its own Content Pipeline tool. The lead developer of FNA has outlined his reasons for omitting this feature. You are STRONGLY encouraged to develop your own content tools, as they will be simpler, easier to use, more specialized and optimized, and will probably take a few days to make, unlike an XNA-like pipeline which would probably take years to complete.

+

That said, FNA is mostly compatible with MGCB, as MGCB creates data that is remarkably close to XNA-generated content (though it is not compatible with XNA). When building MGCB data for FNA, use the DesktopGL configuration.

+

Incompatible Formats

+

The following content formats are incompatible between FNA and MonoGame:

+

Effects. MonoGame opted to create its own shader format, called MGFX. Each platform has its own MGFX file that only works on that platform. FNA continues to support DXBC and Effect framework binaries generated by both the XNA pipeline as well as FXC, the offline compiler for DirectX shaders and effects, from the June 2010 DirectX SDK (ideally; if you have your own FXC from a Windows SDK that works then that's fine too). The one effect binary will work on every platform and backend. To compile an existing XNA effect file with FXC:

+
fxc.exe /T fx_2_0 MyEffect.fx /Fo MyEffect.fxb
+
+

You can also call FXC as part of your MSBuild process, as Andrew Russell's FNA template does.

+

Note that FXC is usable with Wine, meaning shaders can be developed on Linux and macOS.

+

Songs/Videos. MonoGame has various formats and backends used on various platforms, but FNA uses Ogg Vorbis (or QOA) and Ogg Theora for all platforms.

+

MGCB FNA Support

+

Lennard Fonteijn has provided specialized code for MGCB that should allow for the use of MGCB with FNA, even for the formats listed above. You can find this code here:

+

OggSongProcessor and FxcEffectProcessor for FNA and MGCB

+

Platform Compatibility

+

As a reminder, while MonoGame targets each platform with a different project version, FNA supports all of its platforms with exactly one project with no special versions.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
PlatformFNAMonoGame
Windows Direct3D✔️✔️
Windows Vulkan✔️
Windows OpenGL✔️✔️
Linux Vulkan✔️
Linux OpenGL✔️✔️
macOS Metal✔️
macOS OpenGL✔️✔️
iOS/tvOS Metal✔️
iOS/tvOS OpenGL✔️✔️
Nintendo Switch [1]✔️✔️
Xbox One [2]✔️✔️
Xbox Series X/S✔️🚧
PlayStation 4🚧✔️
PlayStation 5🚧✔️
Android❌[3]✔️
GGP (formerly Stadia)✔️🚧
+
    +
  1. For Nintendo Switch, FNA uses Vulkan/OpenGL while MonoGame uses NVN.
  2. +
  3. For Xbox One, FNA uses GDK and MonoGame uses XDK.
  4. +
  5. Android FNA is unofficially supported via FNADroid
  6. +
+ +
+
+ +
+
+ +
+ +
+ +
+ + + + GitHub + + + + « Previous + + + Next » + + +
+ + + + + + + + + diff --git a/appendix/Appendix-E:-NativeAOT-on-PC/index.html b/appendix/Appendix-E:-NativeAOT-on-PC/index.html new file mode 100644 index 0000000..918312f --- /dev/null +++ b/appendix/Appendix-E:-NativeAOT-on-PC/index.html @@ -0,0 +1,263 @@ + + + + + + + + Appendix E: NativeAOT on PC - FNA Docs + + + + + + + + + + + + + + +
+ + +
+ +
+
+ +
+
+
+
+ +

Appendix E: NativeAOT on PC

+

FNA now has support for NativeAOT, a new .NET toolchain which allows you to build your game into an ahead-of-time compiled native executable.

+

Project Setup

+

To get started, please read through the official NativeAOT documentation. Make sure to install the prerequisites listed for your OS.

+

To make your .csproj compatible with NativeAOT, add the following:

+
  <PropertyGroup>
+    <PublishAot>true</PublishAot>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <RdXmlFile Include="rd.xml" />
+  </ItemGroup>
+
+  <ItemGroup Condition="'$(OS)' != 'Windows_NT'">
+    <NativeLibrary Include="-lSDL2" />
+    <NativeLibrary Include="-lFNA3D" />
+    <NativeLibrary Include="-lFAudio" />
+    <NativeLibrary Include="-ltheorafile" />
+  </ItemGroup>
+
+  <ItemGroup Condition="'$(OS)' == 'Windows_NT'">
+    <NativeLibrary Include="SDL2.lib" />
+    <NativeLibrary Include="FNA3D.lib" />
+    <NativeLibrary Include="FAudio.lib" />
+    <NativeLibrary Include="libtheorafile.lib" />
+  </ItemGroup>
+
+  <ItemGroup>
+    <DirectPInvokeList Include="SDLApis.txt" />
+    <DirectPInvoke Include="FNA3D" />
+    <DirectPInvoke Include="FAudio" />
+    <DirectPInvoke Include="libtheorafile" />
+  </ItemGroup>
+
+

You will also need to add two more files to your project directory: rd.xml and SDLApis.txt. These will be explained in the next sections.

+

AOT Type Preservation

+

The rd.xml file informs the compiler of any types it should preserve during the linking stage. This is most often used to preserve types that are only accessed via reflection. You can read the official doc page to learn more about it, but here's an example of what rd.xml might look like for a game that uses ContentReader to load a couple of generic types:

+
<Directives>
+    <Application>
+        <Assembly Name="FNA">
+          <Type Name="Microsoft.Xna.Framework.Content.ListReader`1[[System.Char,mscorlib]]" Dynamic="Required All" />
+          <Type Name="Microsoft.Xna.Framework.Content.ArrayReader`1[[Microsoft.Xna.Framework.Vector3,FNA]]" Dynamic="Required All" />
+        </Assembly>
+        <Assembly Name="mscorlib" />
+    </Application>
+</Directives>
+
+

Native Libraries

+

Even though we're AOT-compiling the project, we still recommend dynamically linking the native libraries rather than statically linking them. Note that this does not mean dynamic loading; the NativeLibrary and DirectPInvoke items in the .csproj ensure that native calls are inlined directly into the executable, rather than requiring a dlopen call to load the library at runtime. This is more performant and reliable than the standard .NET PInvoke system, and is only possible with AOT.

+

FNA3D, FAudio, and Theorafile can be linked in their entirety, but SDL2 is a special case, since some of its public APIs that are only available on particular platforms. In order to ensure we don't link with any platform-specific SDL APIs, we have written a small program that automatically generates a list of all cross-platform functions. The NativeAOT toolchain will consume this and only link the functions listed.

+

If you don't want to run the program yourself, you can just download the latest list that we've generated from here: SDLApis.txt. Then place the file in the same directory as the .csproj.

+

Finally, to actually link the fnalibs, follow these platform-specific instructions:

+
    +
  • Windows:
      +
    • Download the MSVC development build of SDL2, then use it to build the other libraries from source.
    • +
    • Grab the .lib files from SDL2, FNA3D, FAudio, and Theorafile and place them in your app's .csproj directory.
    • +
    • Build the application.
    • +
    • Copy the contents of fnalibs/x64 into the generated output directory.
    • +
    +
  • +
  • MacOS:
      +
    • Build SDL2 from source or install the SDL2 development package from a package manager, then use it to build the other libraries from source.
    • +
    • Copy the resulting *.dylib files from SDL2, FNA3D, FAudio, and Theorafile into /usr/local/lib.
    • +
    • Build the application.
    • +
    • Copy the contents of fnalibs/osx into the generated output directory.
    • +
    • Finally, to ensure your application uses the correct search path for SDL2, use install_name_tool -change /usr/local/lib/libSDL2-2.0.0.dylib @rpath/libSDL2-2.0.0.dylib <my-app-name>.
    • +
    +
  • +
  • Linux:
      +
    • NOTE: For maximum compatibility, we recommend you build using a distro with a low glibc version, like Rocky Linux 8.
    • +
    • Build SDL2 from source or install the SDL2 development package from a package manager, then use it to build FNA3D, FAudio, and Theorafile from source.
    • +
    • Copy all the resulting *.so files into your LD_LIBRARY_PATH (e.g. /usr/local/lib64). Make sure the symversioning is preserved during the copy!
    • +
    • Build the application.
    • +
    • Copy the contents of fnalibs/lib64 into the generated output directory.
    • +
    +
  • +
+ +
+
+ +
+
+ +
+ +
+ +
+ + + + GitHub + + + + « Previous + + + Next » + + +
+ + + + + + + + + diff --git a/appendix/Appendix-F:-Upcoming-Support-Changes/index.html b/appendix/Appendix-F:-Upcoming-Support-Changes/index.html new file mode 100644 index 0000000..667347e --- /dev/null +++ b/appendix/Appendix-F:-Upcoming-Support-Changes/index.html @@ -0,0 +1,223 @@ + + + + + + + + Appendix F: Upcoming Support Changes - FNA Docs + + + + + + + + + + + + + + +
+ + +
+ +
+
+
    +
  • + + +
  • + Edit on GitHub +
  • +
+
+
+
+
+ +

Appendix F: Upcoming Support Changes

+

While very rare, FNA does occasionally make changes to its support matrix, usually to migrate existing platforms to new system requirements or development environments. In extremely rare cases, certain features may even be removed from FNA. This page is meant to track the more extreme cases, so that developers can plan their own products' development accordingly.

+

glibc Support Calendar

+

As of April 1, 2023, FNA requires glibc 2.28 or newer. The fnalibs build OS is RHEL8-based.

+

On April 1, 2025, glibc 2.34 will be required. The fnalibs build OS will be RHEL9-based.

+

"RHEL-based" OSes include RHEL for Developers, Rocky Linux, and AlmaLinux.

+

macOS Support (Single-Assembly)

+

Apple has distanced themselves (and the Mac) significantly from the traditional PC - as of today "PC" is defined as desktop computers running Windows and Linux, as well as Intel Macs running Mojave or older. As part of this, Apple have put significant work into unifying macOS, iOS, and tvOS as part of the Mac's transition to Apple Silicon CPUs. Additionally, the codesign/notarization requirements have increased substantially, meaning the development model used for Linux and Windows will no longer be practical for macOS.

+

Upon the release of SDL 3.0 (or April 1, 2025, whichever is sooner), fnalibs and MonoKickstart will no longer provide binaries for macOS, and support/development will be migrated to the same process as iOS and tvOS. The downside is that single-assembly portability support will likely no longer include macOS, but the upside is that if you can manage to target one Apple OS, you will likely be able to target them all at once (provided you can navigate your way through Apple's usual quirks and demands).

+

FNA3D and SDL_gpu

+

Ryan Gordon is, with the aid of an Epic Megagrant, developing a next-gen GPU abstraction layer in SDL. SDL_gpu will at minimum be targeting Vulkan, Metal, Direct3D 12, and GNM, covering all of the platforms that FNA supports!

+

The FNA team is fully committed to supporting and co-developing SDL_gpu with Ryan, and we want it to be how we handle platform graphics in FNA3D going forward. With this in mind, it's worth looking at our existing renderers and where they fit in this plan:

+

Vulkan

+

Our Vulkan renderer is now stable, and it performs very well on the hardware that supports it. That said, it is an immense support burden and requires a team of very experienced developers to maintain.

+

We are using FNA3D Vulkan as the basis for our input in SDL_gpu's design - we now have a very good picture of what is useful to us in explicit graphics APIs, and we know what a good app implementation of Vulkan looks like. The short version: It's a lot of work, but there's very little that can't be done in a shared library like SDL.

+

The goal is to make it so that we can write an SDL_gpu renderer that is as good (or better!) than the direct Vulkan implementation we have now, so that Vulkan development can be done within SDL instead. With this in mind, it is likely we will remove the Vulkan renderer when SDL_gpu is done, but in exchange SDL_gpu will be made the default renderer on all platforms immediately.

+

In addition to being easier to maintain (there are a lot more SDL developers than FNA3D developers!), this also allows us to target Metal directly, so MoltenVK will no longer be necessary on Apple platforms.

+

D3D11

+

This is less likely to be removed than Vulkan, but it is still worth looking at where D3D11 fits into FNA's support matrix:

+
    +
  • Windows 11 and GDKX require Direct3D 12 hardware
  • +
  • Windows 8.1 and 10 will be fully EOL'd by the time SDL 3.0 ships
  • +
  • Windows 7 will have been EOL'd for almost half a decade!
  • +
  • Starting January 1, 2024, Windows 8.1 and older will no longer be supported by the Steam client (and likely other CEF-based game launchers)
  • +
+

And that's just the software side - on the hardware side, hardware that only supports the D3D11 runtime has become exceedingly rare, and it will only get rarer as the years go on. To put it in perspective: All hardware released in the last 10 years has Vulkan support of some kind!

+

Lastly, it has been proposed that SDL_gpu will have a D3D11 backend. This isn't set in stone like D3D12 is, but if it does happen it will add to the numerous reasons to remove the D3D11 renderer in FNA3D.

+

OpenGL

+

This is by far the least likely to be removed; it has the broadest support, is highly stable, continues to be competitive in performance even compared to Vulkan, and the team is extremely familiar with the implementation in FNA3D. Additionally, it is extremely unlikely that SDL_gpu will have OpenGL support, as the feature set required to make a good backend overlaps completely with Vulkan support, making the backend entirely redundant.

+

Most importantly, OpenGL is the only renderer that supports full XNA preservation as of writing. It is the only renderer that has full support for the pipeline linkage that XNA allowed: You can have vertex input layouts that do not match the vertex shader input, and the pixel shader input layout does not have to match the vertex shader output layout. Our renderers handle the latter category, but only OpenGL supports the former. That's not to say that it's impossible for the other renderers to support this, it is just very difficult to do so. (You ever written a shader linker before? It's no fun.)

+

However, it could be possible to add such support into SDL_gpu. If we are able to support flexible vertex/pixel shader input layouts in the SDL_gpu shader linker, OpenGL will no longer have any benefits other than broad support, which like D3D11 will only become less true over time.

+

If SDL_gpu ends up being the only renderer left, we will remove the FNA3D_Driver system and optimize FNA3D's architecture to be an XNA-friendly frontend to SDL_gpu, and all platform graphics work will happen in SDL (and MojoShader, for optimized shader compilation) from that point onward.

+ +
+
+ +
+
+ +
+ +
+ +
+ + + + GitHub + + + + « Previous + + + +
+ + + + + + + + + diff --git a/css/fonts/Roboto-Slab-Bold.woff b/css/fonts/Roboto-Slab-Bold.woff new file mode 100644 index 0000000..6cb6000 Binary files /dev/null and b/css/fonts/Roboto-Slab-Bold.woff differ diff --git a/css/fonts/Roboto-Slab-Bold.woff2 b/css/fonts/Roboto-Slab-Bold.woff2 new file mode 100644 index 0000000..7059e23 Binary files /dev/null and b/css/fonts/Roboto-Slab-Bold.woff2 differ diff --git a/css/fonts/Roboto-Slab-Regular.woff b/css/fonts/Roboto-Slab-Regular.woff new file mode 100644 index 0000000..f815f63 Binary files /dev/null and b/css/fonts/Roboto-Slab-Regular.woff differ diff --git a/css/fonts/Roboto-Slab-Regular.woff2 b/css/fonts/Roboto-Slab-Regular.woff2 new file mode 100644 index 0000000..f2c76e5 Binary files /dev/null and b/css/fonts/Roboto-Slab-Regular.woff2 differ diff --git a/css/fonts/fontawesome-webfont.eot b/css/fonts/fontawesome-webfont.eot new file mode 100644 index 0000000..e9f60ca Binary files /dev/null and b/css/fonts/fontawesome-webfont.eot differ diff --git a/css/fonts/fontawesome-webfont.svg b/css/fonts/fontawesome-webfont.svg new file mode 100644 index 0000000..855c845 --- /dev/null +++ b/css/fonts/fontawesome-webfont.svg @@ -0,0 +1,2671 @@ + + + + +Created by FontForge 20120731 at Mon Oct 24 17:37:40 2016 + By ,,, +Copyright Dave Gandy 2016. All rights reserved. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/css/fonts/fontawesome-webfont.ttf b/css/fonts/fontawesome-webfont.ttf new file mode 100644 index 0000000..35acda2 Binary files /dev/null and b/css/fonts/fontawesome-webfont.ttf differ diff --git a/css/fonts/fontawesome-webfont.woff b/css/fonts/fontawesome-webfont.woff new file mode 100644 index 0000000..400014a Binary files /dev/null and b/css/fonts/fontawesome-webfont.woff differ diff --git a/css/fonts/fontawesome-webfont.woff2 b/css/fonts/fontawesome-webfont.woff2 new file mode 100644 index 0000000..4d13fc6 Binary files /dev/null and b/css/fonts/fontawesome-webfont.woff2 differ diff --git a/css/fonts/lato-bold-italic.woff b/css/fonts/lato-bold-italic.woff new file mode 100644 index 0000000..88ad05b Binary files /dev/null and b/css/fonts/lato-bold-italic.woff differ diff --git a/css/fonts/lato-bold-italic.woff2 b/css/fonts/lato-bold-italic.woff2 new file mode 100644 index 0000000..c4e3d80 Binary files /dev/null and b/css/fonts/lato-bold-italic.woff2 differ diff --git a/css/fonts/lato-bold.woff b/css/fonts/lato-bold.woff new file mode 100644 index 0000000..c6dff51 Binary files /dev/null and b/css/fonts/lato-bold.woff differ diff --git a/css/fonts/lato-bold.woff2 b/css/fonts/lato-bold.woff2 new file mode 100644 index 0000000..bb19504 Binary files /dev/null and b/css/fonts/lato-bold.woff2 differ diff --git a/css/fonts/lato-normal-italic.woff b/css/fonts/lato-normal-italic.woff new file mode 100644 index 0000000..76114bc Binary files /dev/null and b/css/fonts/lato-normal-italic.woff differ diff --git a/css/fonts/lato-normal-italic.woff2 b/css/fonts/lato-normal-italic.woff2 new file mode 100644 index 0000000..3404f37 Binary files /dev/null and b/css/fonts/lato-normal-italic.woff2 differ diff --git a/css/fonts/lato-normal.woff b/css/fonts/lato-normal.woff new file mode 100644 index 0000000..ae1307f Binary files /dev/null and b/css/fonts/lato-normal.woff differ diff --git a/css/fonts/lato-normal.woff2 b/css/fonts/lato-normal.woff2 new file mode 100644 index 0000000..3bf9843 Binary files /dev/null and b/css/fonts/lato-normal.woff2 differ diff --git a/css/styles.css b/css/styles.css new file mode 100644 index 0000000..f20f2ce --- /dev/null +++ b/css/styles.css @@ -0,0 +1,12 @@ +.wy-side-nav-search { + + background-color: #7933ea; + +} + + +a:hover { + + text-decoration: underline; + +} \ No newline at end of file diff --git a/css/theme.css b/css/theme.css new file mode 100644 index 0000000..ad77300 --- /dev/null +++ b/css/theme.css @@ -0,0 +1,13 @@ +/* + * This file is copied from the upstream ReadTheDocs Sphinx + * theme. To aid upgradability this file should *not* be edited. + * modifications we need should be included in theme_extra.css. + * + * https://github.com/readthedocs/sphinx_rtd_theme + */ + + /* sphinx_rtd_theme version 1.2.0 | MIT license */ +html{box-sizing:border-box}*,:after,:before{box-sizing:inherit}article,aside,details,figcaption,figure,footer,header,hgroup,nav,section{display:block}audio,canvas,video{display:inline-block;*display:inline;*zoom:1}[hidden],audio:not([controls]){display:none}*{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}html{font-size:100%;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}body{margin:0}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:700}blockquote{margin:0}dfn{font-style:italic}ins{background:#ff9;text-decoration:none}ins,mark{color:#000}mark{background:#ff0;font-style:italic;font-weight:700}.rst-content code,.rst-content tt,code,kbd,pre,samp{font-family:monospace,serif;_font-family:courier new,monospace;font-size:1em}pre{white-space:pre}q{quotes:none}q:after,q:before{content:"";content:none}small{font-size:85%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}dl,ol,ul{margin:0;padding:0;list-style:none;list-style-image:none}li{list-style:none}dd{margin:0}img{border:0;-ms-interpolation-mode:bicubic;vertical-align:middle;max-width:100%}svg:not(:root){overflow:hidden}figure,form{margin:0}label{cursor:pointer}button,input,select,textarea{font-size:100%;margin:0;vertical-align:baseline;*vertical-align:middle}button,input{line-height:normal}button,input[type=button],input[type=reset],input[type=submit]{cursor:pointer;-webkit-appearance:button;*overflow:visible}button[disabled],input[disabled]{cursor:default}input[type=search]{-webkit-appearance:textfield;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box}textarea{resize:vertical}table{border-collapse:collapse;border-spacing:0}td{vertical-align:top}.chromeframe{margin:.2em 0;background:#ccc;color:#000;padding:.2em 0}.ir{display:block;border:0;text-indent:-999em;overflow:hidden;background-color:transparent;background-repeat:no-repeat;text-align:left;direction:ltr;*line-height:0}.ir br{display:none}.hidden{display:none!important;visibility:hidden}.visuallyhidden{border:0;clip:rect(0 0 0 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.visuallyhidden.focusable:active,.visuallyhidden.focusable:focus{clip:auto;height:auto;margin:0;overflow:visible;position:static;width:auto}.invisible{visibility:hidden}.relative{position:relative}big,small{font-size:100%}@media print{body,html,section{background:none!important}*{box-shadow:none!important;text-shadow:none!important;filter:none!important;-ms-filter:none!important}a,a:visited{text-decoration:underline}.ir a:after,a[href^="#"]:after,a[href^="javascript:"]:after{content:""}blockquote,pre{page-break-inside:avoid}thead{display:table-header-group}img,tr{page-break-inside:avoid}img{max-width:100%!important}@page{margin:.5cm}.rst-content .toctree-wrapper>p.caption,h2,h3,p{orphans:3;widows:3}.rst-content .toctree-wrapper>p.caption,h2,h3{page-break-after:avoid}}.btn,.fa:before,.icon:before,.rst-content .admonition,.rst-content .admonition-title:before,.rst-content .admonition-todo,.rst-content .attention,.rst-content .caution,.rst-content .code-block-caption .headerlink:before,.rst-content .danger,.rst-content .eqno .headerlink:before,.rst-content .error,.rst-content .hint,.rst-content .important,.rst-content .note,.rst-content .seealso,.rst-content .tip,.rst-content .warning,.rst-content code.download span:first-child:before,.rst-content dl dt .headerlink:before,.rst-content h1 .headerlink:before,.rst-content h2 .headerlink:before,.rst-content h3 .headerlink:before,.rst-content h4 .headerlink:before,.rst-content h5 .headerlink:before,.rst-content h6 .headerlink:before,.rst-content p.caption .headerlink:before,.rst-content p .headerlink:before,.rst-content table>caption .headerlink:before,.rst-content tt.download span:first-child:before,.wy-alert,.wy-dropdown .caret:before,.wy-inline-validate.wy-inline-validate-danger .wy-input-context:before,.wy-inline-validate.wy-inline-validate-info .wy-input-context:before,.wy-inline-validate.wy-inline-validate-success .wy-input-context:before,.wy-inline-validate.wy-inline-validate-warning .wy-input-context:before,.wy-menu-vertical li.current>a button.toctree-expand:before,.wy-menu-vertical li.on a button.toctree-expand:before,.wy-menu-vertical li button.toctree-expand:before,input[type=color],input[type=date],input[type=datetime-local],input[type=datetime],input[type=email],input[type=month],input[type=number],input[type=password],input[type=search],input[type=tel],input[type=text],input[type=time],input[type=url],input[type=week],select,textarea{-webkit-font-smoothing:antialiased}.clearfix{*zoom:1}.clearfix:after,.clearfix:before{display:table;content:""}.clearfix:after{clear:both}/*! + * Font Awesome 4.7.0 by @davegandy - http://fontawesome.io - @fontawesome + * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License) + */@font-face{font-family:FontAwesome;src:url(fonts/fontawesome-webfont.eot?674f50d287a8c48dc19ba404d20fe713);src:url(fonts/fontawesome-webfont.eot?674f50d287a8c48dc19ba404d20fe713?#iefix&v=4.7.0) format("embedded-opentype"),url(fonts/fontawesome-webfont.woff2?af7ae505a9eed503f8b8e6982036873e) format("woff2"),url(fonts/fontawesome-webfont.woff?fee66e712a8a08eef5805a46892932ad) format("woff"),url(fonts/fontawesome-webfont.ttf?b06871f281fee6b241d60582ae9369b9) format("truetype"),url(fonts/fontawesome-webfont.svg?912ec66d7572ff821749319396470bde#fontawesomeregular) format("svg");font-weight:400;font-style:normal}.fa,.icon,.rst-content .admonition-title,.rst-content .code-block-caption .headerlink,.rst-content .eqno .headerlink,.rst-content code.download span:first-child,.rst-content dl dt .headerlink,.rst-content h1 .headerlink,.rst-content h2 .headerlink,.rst-content h3 .headerlink,.rst-content h4 .headerlink,.rst-content h5 .headerlink,.rst-content h6 .headerlink,.rst-content p.caption .headerlink,.rst-content p .headerlink,.rst-content table>caption .headerlink,.rst-content tt.download span:first-child,.wy-menu-vertical li.current>a button.toctree-expand,.wy-menu-vertical li.on a button.toctree-expand,.wy-menu-vertical li button.toctree-expand{display:inline-block;font:normal normal normal 14px/1 FontAwesome;font-size:inherit;text-rendering:auto;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.fa-lg{font-size:1.33333em;line-height:.75em;vertical-align:-15%}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-fw{width:1.28571em;text-align:center}.fa-ul{padding-left:0;margin-left:2.14286em;list-style-type:none}.fa-ul>li{position:relative}.fa-li{position:absolute;left:-2.14286em;width:2.14286em;top:.14286em;text-align:center}.fa-li.fa-lg{left:-1.85714em}.fa-border{padding:.2em .25em .15em;border:.08em solid #eee;border-radius:.1em}.fa-pull-left{float:left}.fa-pull-right{float:right}.fa-pull-left.icon,.fa.fa-pull-left,.rst-content .code-block-caption .fa-pull-left.headerlink,.rst-content .eqno .fa-pull-left.headerlink,.rst-content .fa-pull-left.admonition-title,.rst-content code.download span.fa-pull-left:first-child,.rst-content dl dt .fa-pull-left.headerlink,.rst-content h1 .fa-pull-left.headerlink,.rst-content h2 .fa-pull-left.headerlink,.rst-content h3 .fa-pull-left.headerlink,.rst-content h4 .fa-pull-left.headerlink,.rst-content h5 .fa-pull-left.headerlink,.rst-content h6 .fa-pull-left.headerlink,.rst-content p .fa-pull-left.headerlink,.rst-content table>caption .fa-pull-left.headerlink,.rst-content tt.download span.fa-pull-left:first-child,.wy-menu-vertical li.current>a button.fa-pull-left.toctree-expand,.wy-menu-vertical li.on a button.fa-pull-left.toctree-expand,.wy-menu-vertical li button.fa-pull-left.toctree-expand{margin-right:.3em}.fa-pull-right.icon,.fa.fa-pull-right,.rst-content .code-block-caption .fa-pull-right.headerlink,.rst-content .eqno .fa-pull-right.headerlink,.rst-content .fa-pull-right.admonition-title,.rst-content code.download span.fa-pull-right:first-child,.rst-content dl dt .fa-pull-right.headerlink,.rst-content h1 .fa-pull-right.headerlink,.rst-content h2 .fa-pull-right.headerlink,.rst-content h3 .fa-pull-right.headerlink,.rst-content h4 .fa-pull-right.headerlink,.rst-content h5 .fa-pull-right.headerlink,.rst-content h6 .fa-pull-right.headerlink,.rst-content p .fa-pull-right.headerlink,.rst-content table>caption .fa-pull-right.headerlink,.rst-content tt.download span.fa-pull-right:first-child,.wy-menu-vertical li.current>a button.fa-pull-right.toctree-expand,.wy-menu-vertical li.on a button.fa-pull-right.toctree-expand,.wy-menu-vertical li button.fa-pull-right.toctree-expand{margin-left:.3em}.pull-right{float:right}.pull-left{float:left}.fa.pull-left,.pull-left.icon,.rst-content .code-block-caption .pull-left.headerlink,.rst-content .eqno .pull-left.headerlink,.rst-content .pull-left.admonition-title,.rst-content code.download span.pull-left:first-child,.rst-content dl dt .pull-left.headerlink,.rst-content h1 .pull-left.headerlink,.rst-content h2 .pull-left.headerlink,.rst-content h3 .pull-left.headerlink,.rst-content h4 .pull-left.headerlink,.rst-content h5 .pull-left.headerlink,.rst-content h6 .pull-left.headerlink,.rst-content p .pull-left.headerlink,.rst-content table>caption .pull-left.headerlink,.rst-content tt.download span.pull-left:first-child,.wy-menu-vertical li.current>a button.pull-left.toctree-expand,.wy-menu-vertical li.on a button.pull-left.toctree-expand,.wy-menu-vertical li button.pull-left.toctree-expand{margin-right:.3em}.fa.pull-right,.pull-right.icon,.rst-content .code-block-caption .pull-right.headerlink,.rst-content .eqno .pull-right.headerlink,.rst-content .pull-right.admonition-title,.rst-content code.download span.pull-right:first-child,.rst-content dl dt .pull-right.headerlink,.rst-content h1 .pull-right.headerlink,.rst-content h2 .pull-right.headerlink,.rst-content h3 .pull-right.headerlink,.rst-content h4 .pull-right.headerlink,.rst-content h5 .pull-right.headerlink,.rst-content h6 .pull-right.headerlink,.rst-content p .pull-right.headerlink,.rst-content table>caption .pull-right.headerlink,.rst-content tt.download span.pull-right:first-child,.wy-menu-vertical li.current>a button.pull-right.toctree-expand,.wy-menu-vertical li.on a button.pull-right.toctree-expand,.wy-menu-vertical li button.pull-right.toctree-expand{margin-left:.3em}.fa-spin{-webkit-animation:fa-spin 2s linear infinite;animation:fa-spin 2s linear infinite}.fa-pulse{-webkit-animation:fa-spin 1s steps(8) infinite;animation:fa-spin 1s steps(8) infinite}@-webkit-keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}.fa-rotate-90{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=1)";-webkit-transform:rotate(90deg);-ms-transform:rotate(90deg);transform:rotate(90deg)}.fa-rotate-180{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2)";-webkit-transform:rotate(180deg);-ms-transform:rotate(180deg);transform:rotate(180deg)}.fa-rotate-270{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=3)";-webkit-transform:rotate(270deg);-ms-transform:rotate(270deg);transform:rotate(270deg)}.fa-flip-horizontal{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1)";-webkit-transform:scaleX(-1);-ms-transform:scaleX(-1);transform:scaleX(-1)}.fa-flip-vertical{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)";-webkit-transform:scaleY(-1);-ms-transform:scaleY(-1);transform:scaleY(-1)}:root .fa-flip-horizontal,:root .fa-flip-vertical,:root .fa-rotate-90,:root .fa-rotate-180,:root .fa-rotate-270{filter:none}.fa-stack{position:relative;display:inline-block;width:2em;height:2em;line-height:2em;vertical-align:middle}.fa-stack-1x,.fa-stack-2x{position:absolute;left:0;width:100%;text-align:center}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:#fff}.fa-glass:before{content:""}.fa-music:before{content:""}.fa-search:before,.icon-search:before{content:""}.fa-envelope-o:before{content:""}.fa-heart:before{content:""}.fa-star:before{content:""}.fa-star-o:before{content:""}.fa-user:before{content:""}.fa-film:before{content:""}.fa-th-large:before{content:""}.fa-th:before{content:""}.fa-th-list:before{content:""}.fa-check:before{content:""}.fa-close:before,.fa-remove:before,.fa-times:before{content:""}.fa-search-plus:before{content:""}.fa-search-minus:before{content:""}.fa-power-off:before{content:""}.fa-signal:before{content:""}.fa-cog:before,.fa-gear:before{content:""}.fa-trash-o:before{content:""}.fa-home:before,.icon-home:before{content:""}.fa-file-o:before{content:""}.fa-clock-o:before{content:""}.fa-road:before{content:""}.fa-download:before,.rst-content code.download span:first-child:before,.rst-content tt.download span:first-child:before{content:""}.fa-arrow-circle-o-down:before{content:""}.fa-arrow-circle-o-up:before{content:""}.fa-inbox:before{content:""}.fa-play-circle-o:before{content:""}.fa-repeat:before,.fa-rotate-right:before{content:""}.fa-refresh:before{content:""}.fa-list-alt:before{content:""}.fa-lock:before{content:""}.fa-flag:before{content:""}.fa-headphones:before{content:""}.fa-volume-off:before{content:""}.fa-volume-down:before{content:""}.fa-volume-up:before{content:""}.fa-qrcode:before{content:""}.fa-barcode:before{content:""}.fa-tag:before{content:""}.fa-tags:before{content:""}.fa-book:before,.icon-book:before{content:""}.fa-bookmark:before{content:""}.fa-print:before{content:""}.fa-camera:before{content:""}.fa-font:before{content:""}.fa-bold:before{content:""}.fa-italic:before{content:""}.fa-text-height:before{content:""}.fa-text-width:before{content:""}.fa-align-left:before{content:""}.fa-align-center:before{content:""}.fa-align-right:before{content:""}.fa-align-justify:before{content:""}.fa-list:before{content:""}.fa-dedent:before,.fa-outdent:before{content:""}.fa-indent:before{content:""}.fa-video-camera:before{content:""}.fa-image:before,.fa-photo:before,.fa-picture-o:before{content:""}.fa-pencil:before{content:""}.fa-map-marker:before{content:""}.fa-adjust:before{content:""}.fa-tint:before{content:""}.fa-edit:before,.fa-pencil-square-o:before{content:""}.fa-share-square-o:before{content:""}.fa-check-square-o:before{content:""}.fa-arrows:before{content:""}.fa-step-backward:before{content:""}.fa-fast-backward:before{content:""}.fa-backward:before{content:""}.fa-play:before{content:""}.fa-pause:before{content:""}.fa-stop:before{content:""}.fa-forward:before{content:""}.fa-fast-forward:before{content:""}.fa-step-forward:before{content:""}.fa-eject:before{content:""}.fa-chevron-left:before{content:""}.fa-chevron-right:before{content:""}.fa-plus-circle:before{content:""}.fa-minus-circle:before{content:""}.fa-times-circle:before,.wy-inline-validate.wy-inline-validate-danger .wy-input-context:before{content:""}.fa-check-circle:before,.wy-inline-validate.wy-inline-validate-success .wy-input-context:before{content:""}.fa-question-circle:before{content:""}.fa-info-circle:before{content:""}.fa-crosshairs:before{content:""}.fa-times-circle-o:before{content:""}.fa-check-circle-o:before{content:""}.fa-ban:before{content:""}.fa-arrow-left:before{content:""}.fa-arrow-right:before{content:""}.fa-arrow-up:before{content:""}.fa-arrow-down:before{content:""}.fa-mail-forward:before,.fa-share:before{content:""}.fa-expand:before{content:""}.fa-compress:before{content:""}.fa-plus:before{content:""}.fa-minus:before{content:""}.fa-asterisk:before{content:""}.fa-exclamation-circle:before,.rst-content .admonition-title:before,.wy-inline-validate.wy-inline-validate-info .wy-input-context:before,.wy-inline-validate.wy-inline-validate-warning .wy-input-context:before{content:""}.fa-gift:before{content:""}.fa-leaf:before{content:""}.fa-fire:before,.icon-fire:before{content:""}.fa-eye:before{content:""}.fa-eye-slash:before{content:""}.fa-exclamation-triangle:before,.fa-warning:before{content:""}.fa-plane:before{content:""}.fa-calendar:before{content:""}.fa-random:before{content:""}.fa-comment:before{content:""}.fa-magnet:before{content:""}.fa-chevron-up:before{content:""}.fa-chevron-down:before{content:""}.fa-retweet:before{content:""}.fa-shopping-cart:before{content:""}.fa-folder:before{content:""}.fa-folder-open:before{content:""}.fa-arrows-v:before{content:""}.fa-arrows-h:before{content:""}.fa-bar-chart-o:before,.fa-bar-chart:before{content:""}.fa-twitter-square:before{content:""}.fa-facebook-square:before{content:""}.fa-camera-retro:before{content:""}.fa-key:before{content:""}.fa-cogs:before,.fa-gears:before{content:""}.fa-comments:before{content:""}.fa-thumbs-o-up:before{content:""}.fa-thumbs-o-down:before{content:""}.fa-star-half:before{content:""}.fa-heart-o:before{content:""}.fa-sign-out:before{content:""}.fa-linkedin-square:before{content:""}.fa-thumb-tack:before{content:""}.fa-external-link:before{content:""}.fa-sign-in:before{content:""}.fa-trophy:before{content:""}.fa-github-square:before{content:""}.fa-upload:before{content:""}.fa-lemon-o:before{content:""}.fa-phone:before{content:""}.fa-square-o:before{content:""}.fa-bookmark-o:before{content:""}.fa-phone-square:before{content:""}.fa-twitter:before{content:""}.fa-facebook-f:before,.fa-facebook:before{content:""}.fa-github:before,.icon-github:before{content:""}.fa-unlock:before{content:""}.fa-credit-card:before{content:""}.fa-feed:before,.fa-rss:before{content:""}.fa-hdd-o:before{content:""}.fa-bullhorn:before{content:""}.fa-bell:before{content:""}.fa-certificate:before{content:""}.fa-hand-o-right:before{content:""}.fa-hand-o-left:before{content:""}.fa-hand-o-up:before{content:""}.fa-hand-o-down:before{content:""}.fa-arrow-circle-left:before,.icon-circle-arrow-left:before{content:""}.fa-arrow-circle-right:before,.icon-circle-arrow-right:before{content:""}.fa-arrow-circle-up:before{content:""}.fa-arrow-circle-down:before{content:""}.fa-globe:before{content:""}.fa-wrench:before{content:""}.fa-tasks:before{content:""}.fa-filter:before{content:""}.fa-briefcase:before{content:""}.fa-arrows-alt:before{content:""}.fa-group:before,.fa-users:before{content:""}.fa-chain:before,.fa-link:before,.icon-link:before{content:""}.fa-cloud:before{content:""}.fa-flask:before{content:""}.fa-cut:before,.fa-scissors:before{content:""}.fa-copy:before,.fa-files-o:before{content:""}.fa-paperclip:before{content:""}.fa-floppy-o:before,.fa-save:before{content:""}.fa-square:before{content:""}.fa-bars:before,.fa-navicon:before,.fa-reorder:before{content:""}.fa-list-ul:before{content:""}.fa-list-ol:before{content:""}.fa-strikethrough:before{content:""}.fa-underline:before{content:""}.fa-table:before{content:""}.fa-magic:before{content:""}.fa-truck:before{content:""}.fa-pinterest:before{content:""}.fa-pinterest-square:before{content:""}.fa-google-plus-square:before{content:""}.fa-google-plus:before{content:""}.fa-money:before{content:""}.fa-caret-down:before,.icon-caret-down:before,.wy-dropdown .caret:before{content:""}.fa-caret-up:before{content:""}.fa-caret-left:before{content:""}.fa-caret-right:before{content:""}.fa-columns:before{content:""}.fa-sort:before,.fa-unsorted:before{content:""}.fa-sort-desc:before,.fa-sort-down:before{content:""}.fa-sort-asc:before,.fa-sort-up:before{content:""}.fa-envelope:before{content:""}.fa-linkedin:before{content:""}.fa-rotate-left:before,.fa-undo:before{content:""}.fa-gavel:before,.fa-legal:before{content:""}.fa-dashboard:before,.fa-tachometer:before{content:""}.fa-comment-o:before{content:""}.fa-comments-o:before{content:""}.fa-bolt:before,.fa-flash:before{content:""}.fa-sitemap:before{content:""}.fa-umbrella:before{content:""}.fa-clipboard:before,.fa-paste:before{content:""}.fa-lightbulb-o:before{content:""}.fa-exchange:before{content:""}.fa-cloud-download:before{content:""}.fa-cloud-upload:before{content:""}.fa-user-md:before{content:""}.fa-stethoscope:before{content:""}.fa-suitcase:before{content:""}.fa-bell-o:before{content:""}.fa-coffee:before{content:""}.fa-cutlery:before{content:""}.fa-file-text-o:before{content:""}.fa-building-o:before{content:""}.fa-hospital-o:before{content:""}.fa-ambulance:before{content:""}.fa-medkit:before{content:""}.fa-fighter-jet:before{content:""}.fa-beer:before{content:""}.fa-h-square:before{content:""}.fa-plus-square:before{content:""}.fa-angle-double-left:before{content:""}.fa-angle-double-right:before{content:""}.fa-angle-double-up:before{content:""}.fa-angle-double-down:before{content:""}.fa-angle-left:before{content:""}.fa-angle-right:before{content:""}.fa-angle-up:before{content:""}.fa-angle-down:before{content:""}.fa-desktop:before{content:""}.fa-laptop:before{content:""}.fa-tablet:before{content:""}.fa-mobile-phone:before,.fa-mobile:before{content:""}.fa-circle-o:before{content:""}.fa-quote-left:before{content:""}.fa-quote-right:before{content:""}.fa-spinner:before{content:""}.fa-circle:before{content:""}.fa-mail-reply:before,.fa-reply:before{content:""}.fa-github-alt:before{content:""}.fa-folder-o:before{content:""}.fa-folder-open-o:before{content:""}.fa-smile-o:before{content:""}.fa-frown-o:before{content:""}.fa-meh-o:before{content:""}.fa-gamepad:before{content:""}.fa-keyboard-o:before{content:""}.fa-flag-o:before{content:""}.fa-flag-checkered:before{content:""}.fa-terminal:before{content:""}.fa-code:before{content:""}.fa-mail-reply-all:before,.fa-reply-all:before{content:""}.fa-star-half-empty:before,.fa-star-half-full:before,.fa-star-half-o:before{content:""}.fa-location-arrow:before{content:""}.fa-crop:before{content:""}.fa-code-fork:before{content:""}.fa-chain-broken:before,.fa-unlink:before{content:""}.fa-question:before{content:""}.fa-info:before{content:""}.fa-exclamation:before{content:""}.fa-superscript:before{content:""}.fa-subscript:before{content:""}.fa-eraser:before{content:""}.fa-puzzle-piece:before{content:""}.fa-microphone:before{content:""}.fa-microphone-slash:before{content:""}.fa-shield:before{content:""}.fa-calendar-o:before{content:""}.fa-fire-extinguisher:before{content:""}.fa-rocket:before{content:""}.fa-maxcdn:before{content:""}.fa-chevron-circle-left:before{content:""}.fa-chevron-circle-right:before{content:""}.fa-chevron-circle-up:before{content:""}.fa-chevron-circle-down:before{content:""}.fa-html5:before{content:""}.fa-css3:before{content:""}.fa-anchor:before{content:""}.fa-unlock-alt:before{content:""}.fa-bullseye:before{content:""}.fa-ellipsis-h:before{content:""}.fa-ellipsis-v:before{content:""}.fa-rss-square:before{content:""}.fa-play-circle:before{content:""}.fa-ticket:before{content:""}.fa-minus-square:before{content:""}.fa-minus-square-o:before,.wy-menu-vertical li.current>a button.toctree-expand:before,.wy-menu-vertical li.on a button.toctree-expand:before{content:""}.fa-level-up:before{content:""}.fa-level-down:before{content:""}.fa-check-square:before{content:""}.fa-pencil-square:before{content:""}.fa-external-link-square:before{content:""}.fa-share-square:before{content:""}.fa-compass:before{content:""}.fa-caret-square-o-down:before,.fa-toggle-down:before{content:""}.fa-caret-square-o-up:before,.fa-toggle-up:before{content:""}.fa-caret-square-o-right:before,.fa-toggle-right:before{content:""}.fa-eur:before,.fa-euro:before{content:""}.fa-gbp:before{content:""}.fa-dollar:before,.fa-usd:before{content:""}.fa-inr:before,.fa-rupee:before{content:""}.fa-cny:before,.fa-jpy:before,.fa-rmb:before,.fa-yen:before{content:""}.fa-rouble:before,.fa-rub:before,.fa-ruble:before{content:""}.fa-krw:before,.fa-won:before{content:""}.fa-bitcoin:before,.fa-btc:before{content:""}.fa-file:before{content:""}.fa-file-text:before{content:""}.fa-sort-alpha-asc:before{content:""}.fa-sort-alpha-desc:before{content:""}.fa-sort-amount-asc:before{content:""}.fa-sort-amount-desc:before{content:""}.fa-sort-numeric-asc:before{content:""}.fa-sort-numeric-desc:before{content:""}.fa-thumbs-up:before{content:""}.fa-thumbs-down:before{content:""}.fa-youtube-square:before{content:""}.fa-youtube:before{content:""}.fa-xing:before{content:""}.fa-xing-square:before{content:""}.fa-youtube-play:before{content:""}.fa-dropbox:before{content:""}.fa-stack-overflow:before{content:""}.fa-instagram:before{content:""}.fa-flickr:before{content:""}.fa-adn:before{content:""}.fa-bitbucket:before,.icon-bitbucket:before{content:""}.fa-bitbucket-square:before{content:""}.fa-tumblr:before{content:""}.fa-tumblr-square:before{content:""}.fa-long-arrow-down:before{content:""}.fa-long-arrow-up:before{content:""}.fa-long-arrow-left:before{content:""}.fa-long-arrow-right:before{content:""}.fa-apple:before{content:""}.fa-windows:before{content:""}.fa-android:before{content:""}.fa-linux:before{content:""}.fa-dribbble:before{content:""}.fa-skype:before{content:""}.fa-foursquare:before{content:""}.fa-trello:before{content:""}.fa-female:before{content:""}.fa-male:before{content:""}.fa-gittip:before,.fa-gratipay:before{content:""}.fa-sun-o:before{content:""}.fa-moon-o:before{content:""}.fa-archive:before{content:""}.fa-bug:before{content:""}.fa-vk:before{content:""}.fa-weibo:before{content:""}.fa-renren:before{content:""}.fa-pagelines:before{content:""}.fa-stack-exchange:before{content:""}.fa-arrow-circle-o-right:before{content:""}.fa-arrow-circle-o-left:before{content:""}.fa-caret-square-o-left:before,.fa-toggle-left:before{content:""}.fa-dot-circle-o:before{content:""}.fa-wheelchair:before{content:""}.fa-vimeo-square:before{content:""}.fa-try:before,.fa-turkish-lira:before{content:""}.fa-plus-square-o:before,.wy-menu-vertical li button.toctree-expand:before{content:""}.fa-space-shuttle:before{content:""}.fa-slack:before{content:""}.fa-envelope-square:before{content:""}.fa-wordpress:before{content:""}.fa-openid:before{content:""}.fa-bank:before,.fa-institution:before,.fa-university:before{content:""}.fa-graduation-cap:before,.fa-mortar-board:before{content:""}.fa-yahoo:before{content:""}.fa-google:before{content:""}.fa-reddit:before{content:""}.fa-reddit-square:before{content:""}.fa-stumbleupon-circle:before{content:""}.fa-stumbleupon:before{content:""}.fa-delicious:before{content:""}.fa-digg:before{content:""}.fa-pied-piper-pp:before{content:""}.fa-pied-piper-alt:before{content:""}.fa-drupal:before{content:""}.fa-joomla:before{content:""}.fa-language:before{content:""}.fa-fax:before{content:""}.fa-building:before{content:""}.fa-child:before{content:""}.fa-paw:before{content:""}.fa-spoon:before{content:""}.fa-cube:before{content:""}.fa-cubes:before{content:""}.fa-behance:before{content:""}.fa-behance-square:before{content:""}.fa-steam:before{content:""}.fa-steam-square:before{content:""}.fa-recycle:before{content:""}.fa-automobile:before,.fa-car:before{content:""}.fa-cab:before,.fa-taxi:before{content:""}.fa-tree:before{content:""}.fa-spotify:before{content:""}.fa-deviantart:before{content:""}.fa-soundcloud:before{content:""}.fa-database:before{content:""}.fa-file-pdf-o:before{content:""}.fa-file-word-o:before{content:""}.fa-file-excel-o:before{content:""}.fa-file-powerpoint-o:before{content:""}.fa-file-image-o:before,.fa-file-photo-o:before,.fa-file-picture-o:before{content:""}.fa-file-archive-o:before,.fa-file-zip-o:before{content:""}.fa-file-audio-o:before,.fa-file-sound-o:before{content:""}.fa-file-movie-o:before,.fa-file-video-o:before{content:""}.fa-file-code-o:before{content:""}.fa-vine:before{content:""}.fa-codepen:before{content:""}.fa-jsfiddle:before{content:""}.fa-life-bouy:before,.fa-life-buoy:before,.fa-life-ring:before,.fa-life-saver:before,.fa-support:before{content:""}.fa-circle-o-notch:before{content:""}.fa-ra:before,.fa-rebel:before,.fa-resistance:before{content:""}.fa-empire:before,.fa-ge:before{content:""}.fa-git-square:before{content:""}.fa-git:before{content:""}.fa-hacker-news:before,.fa-y-combinator-square:before,.fa-yc-square:before{content:""}.fa-tencent-weibo:before{content:""}.fa-qq:before{content:""}.fa-wechat:before,.fa-weixin:before{content:""}.fa-paper-plane:before,.fa-send:before{content:""}.fa-paper-plane-o:before,.fa-send-o:before{content:""}.fa-history:before{content:""}.fa-circle-thin:before{content:""}.fa-header:before{content:""}.fa-paragraph:before{content:""}.fa-sliders:before{content:""}.fa-share-alt:before{content:""}.fa-share-alt-square:before{content:""}.fa-bomb:before{content:""}.fa-futbol-o:before,.fa-soccer-ball-o:before{content:""}.fa-tty:before{content:""}.fa-binoculars:before{content:""}.fa-plug:before{content:""}.fa-slideshare:before{content:""}.fa-twitch:before{content:""}.fa-yelp:before{content:""}.fa-newspaper-o:before{content:""}.fa-wifi:before{content:""}.fa-calculator:before{content:""}.fa-paypal:before{content:""}.fa-google-wallet:before{content:""}.fa-cc-visa:before{content:""}.fa-cc-mastercard:before{content:""}.fa-cc-discover:before{content:""}.fa-cc-amex:before{content:""}.fa-cc-paypal:before{content:""}.fa-cc-stripe:before{content:""}.fa-bell-slash:before{content:""}.fa-bell-slash-o:before{content:""}.fa-trash:before{content:""}.fa-copyright:before{content:""}.fa-at:before{content:""}.fa-eyedropper:before{content:""}.fa-paint-brush:before{content:""}.fa-birthday-cake:before{content:""}.fa-area-chart:before{content:""}.fa-pie-chart:before{content:""}.fa-line-chart:before{content:""}.fa-lastfm:before{content:""}.fa-lastfm-square:before{content:""}.fa-toggle-off:before{content:""}.fa-toggle-on:before{content:""}.fa-bicycle:before{content:""}.fa-bus:before{content:""}.fa-ioxhost:before{content:""}.fa-angellist:before{content:""}.fa-cc:before{content:""}.fa-ils:before,.fa-shekel:before,.fa-sheqel:before{content:""}.fa-meanpath:before{content:""}.fa-buysellads:before{content:""}.fa-connectdevelop:before{content:""}.fa-dashcube:before{content:""}.fa-forumbee:before{content:""}.fa-leanpub:before{content:""}.fa-sellsy:before{content:""}.fa-shirtsinbulk:before{content:""}.fa-simplybuilt:before{content:""}.fa-skyatlas:before{content:""}.fa-cart-plus:before{content:""}.fa-cart-arrow-down:before{content:""}.fa-diamond:before{content:""}.fa-ship:before{content:""}.fa-user-secret:before{content:""}.fa-motorcycle:before{content:""}.fa-street-view:before{content:""}.fa-heartbeat:before{content:""}.fa-venus:before{content:""}.fa-mars:before{content:""}.fa-mercury:before{content:""}.fa-intersex:before,.fa-transgender:before{content:""}.fa-transgender-alt:before{content:""}.fa-venus-double:before{content:""}.fa-mars-double:before{content:""}.fa-venus-mars:before{content:""}.fa-mars-stroke:before{content:""}.fa-mars-stroke-v:before{content:""}.fa-mars-stroke-h:before{content:""}.fa-neuter:before{content:""}.fa-genderless:before{content:""}.fa-facebook-official:before{content:""}.fa-pinterest-p:before{content:""}.fa-whatsapp:before{content:""}.fa-server:before{content:""}.fa-user-plus:before{content:""}.fa-user-times:before{content:""}.fa-bed:before,.fa-hotel:before{content:""}.fa-viacoin:before{content:""}.fa-train:before{content:""}.fa-subway:before{content:""}.fa-medium:before{content:""}.fa-y-combinator:before,.fa-yc:before{content:""}.fa-optin-monster:before{content:""}.fa-opencart:before{content:""}.fa-expeditedssl:before{content:""}.fa-battery-4:before,.fa-battery-full:before,.fa-battery:before{content:""}.fa-battery-3:before,.fa-battery-three-quarters:before{content:""}.fa-battery-2:before,.fa-battery-half:before{content:""}.fa-battery-1:before,.fa-battery-quarter:before{content:""}.fa-battery-0:before,.fa-battery-empty:before{content:""}.fa-mouse-pointer:before{content:""}.fa-i-cursor:before{content:""}.fa-object-group:before{content:""}.fa-object-ungroup:before{content:""}.fa-sticky-note:before{content:""}.fa-sticky-note-o:before{content:""}.fa-cc-jcb:before{content:""}.fa-cc-diners-club:before{content:""}.fa-clone:before{content:""}.fa-balance-scale:before{content:""}.fa-hourglass-o:before{content:""}.fa-hourglass-1:before,.fa-hourglass-start:before{content:""}.fa-hourglass-2:before,.fa-hourglass-half:before{content:""}.fa-hourglass-3:before,.fa-hourglass-end:before{content:""}.fa-hourglass:before{content:""}.fa-hand-grab-o:before,.fa-hand-rock-o:before{content:""}.fa-hand-paper-o:before,.fa-hand-stop-o:before{content:""}.fa-hand-scissors-o:before{content:""}.fa-hand-lizard-o:before{content:""}.fa-hand-spock-o:before{content:""}.fa-hand-pointer-o:before{content:""}.fa-hand-peace-o:before{content:""}.fa-trademark:before{content:""}.fa-registered:before{content:""}.fa-creative-commons:before{content:""}.fa-gg:before{content:""}.fa-gg-circle:before{content:""}.fa-tripadvisor:before{content:""}.fa-odnoklassniki:before{content:""}.fa-odnoklassniki-square:before{content:""}.fa-get-pocket:before{content:""}.fa-wikipedia-w:before{content:""}.fa-safari:before{content:""}.fa-chrome:before{content:""}.fa-firefox:before{content:""}.fa-opera:before{content:""}.fa-internet-explorer:before{content:""}.fa-television:before,.fa-tv:before{content:""}.fa-contao:before{content:""}.fa-500px:before{content:""}.fa-amazon:before{content:""}.fa-calendar-plus-o:before{content:""}.fa-calendar-minus-o:before{content:""}.fa-calendar-times-o:before{content:""}.fa-calendar-check-o:before{content:""}.fa-industry:before{content:""}.fa-map-pin:before{content:""}.fa-map-signs:before{content:""}.fa-map-o:before{content:""}.fa-map:before{content:""}.fa-commenting:before{content:""}.fa-commenting-o:before{content:""}.fa-houzz:before{content:""}.fa-vimeo:before{content:""}.fa-black-tie:before{content:""}.fa-fonticons:before{content:""}.fa-reddit-alien:before{content:""}.fa-edge:before{content:""}.fa-credit-card-alt:before{content:""}.fa-codiepie:before{content:""}.fa-modx:before{content:""}.fa-fort-awesome:before{content:""}.fa-usb:before{content:""}.fa-product-hunt:before{content:""}.fa-mixcloud:before{content:""}.fa-scribd:before{content:""}.fa-pause-circle:before{content:""}.fa-pause-circle-o:before{content:""}.fa-stop-circle:before{content:""}.fa-stop-circle-o:before{content:""}.fa-shopping-bag:before{content:""}.fa-shopping-basket:before{content:""}.fa-hashtag:before{content:""}.fa-bluetooth:before{content:""}.fa-bluetooth-b:before{content:""}.fa-percent:before{content:""}.fa-gitlab:before,.icon-gitlab:before{content:""}.fa-wpbeginner:before{content:""}.fa-wpforms:before{content:""}.fa-envira:before{content:""}.fa-universal-access:before{content:""}.fa-wheelchair-alt:before{content:""}.fa-question-circle-o:before{content:""}.fa-blind:before{content:""}.fa-audio-description:before{content:""}.fa-volume-control-phone:before{content:""}.fa-braille:before{content:""}.fa-assistive-listening-systems:before{content:""}.fa-american-sign-language-interpreting:before,.fa-asl-interpreting:before{content:""}.fa-deaf:before,.fa-deafness:before,.fa-hard-of-hearing:before{content:""}.fa-glide:before{content:""}.fa-glide-g:before{content:""}.fa-sign-language:before,.fa-signing:before{content:""}.fa-low-vision:before{content:""}.fa-viadeo:before{content:""}.fa-viadeo-square:before{content:""}.fa-snapchat:before{content:""}.fa-snapchat-ghost:before{content:""}.fa-snapchat-square:before{content:""}.fa-pied-piper:before{content:""}.fa-first-order:before{content:""}.fa-yoast:before{content:""}.fa-themeisle:before{content:""}.fa-google-plus-circle:before,.fa-google-plus-official:before{content:""}.fa-fa:before,.fa-font-awesome:before{content:""}.fa-handshake-o:before{content:""}.fa-envelope-open:before{content:""}.fa-envelope-open-o:before{content:""}.fa-linode:before{content:""}.fa-address-book:before{content:""}.fa-address-book-o:before{content:""}.fa-address-card:before,.fa-vcard:before{content:""}.fa-address-card-o:before,.fa-vcard-o:before{content:""}.fa-user-circle:before{content:""}.fa-user-circle-o:before{content:""}.fa-user-o:before{content:""}.fa-id-badge:before{content:""}.fa-drivers-license:before,.fa-id-card:before{content:""}.fa-drivers-license-o:before,.fa-id-card-o:before{content:""}.fa-quora:before{content:""}.fa-free-code-camp:before{content:""}.fa-telegram:before{content:""}.fa-thermometer-4:before,.fa-thermometer-full:before,.fa-thermometer:before{content:""}.fa-thermometer-3:before,.fa-thermometer-three-quarters:before{content:""}.fa-thermometer-2:before,.fa-thermometer-half:before{content:""}.fa-thermometer-1:before,.fa-thermometer-quarter:before{content:""}.fa-thermometer-0:before,.fa-thermometer-empty:before{content:""}.fa-shower:before{content:""}.fa-bath:before,.fa-bathtub:before,.fa-s15:before{content:""}.fa-podcast:before{content:""}.fa-window-maximize:before{content:""}.fa-window-minimize:before{content:""}.fa-window-restore:before{content:""}.fa-times-rectangle:before,.fa-window-close:before{content:""}.fa-times-rectangle-o:before,.fa-window-close-o:before{content:""}.fa-bandcamp:before{content:""}.fa-grav:before{content:""}.fa-etsy:before{content:""}.fa-imdb:before{content:""}.fa-ravelry:before{content:""}.fa-eercast:before{content:""}.fa-microchip:before{content:""}.fa-snowflake-o:before{content:""}.fa-superpowers:before{content:""}.fa-wpexplorer:before{content:""}.fa-meetup:before{content:""}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;margin:0;overflow:visible;clip:auto}.fa,.icon,.rst-content .admonition-title,.rst-content .code-block-caption .headerlink,.rst-content .eqno .headerlink,.rst-content code.download span:first-child,.rst-content dl dt .headerlink,.rst-content h1 .headerlink,.rst-content h2 .headerlink,.rst-content h3 .headerlink,.rst-content h4 .headerlink,.rst-content h5 .headerlink,.rst-content h6 .headerlink,.rst-content p.caption .headerlink,.rst-content p .headerlink,.rst-content table>caption .headerlink,.rst-content tt.download span:first-child,.wy-dropdown .caret,.wy-inline-validate.wy-inline-validate-danger .wy-input-context,.wy-inline-validate.wy-inline-validate-info .wy-input-context,.wy-inline-validate.wy-inline-validate-success .wy-input-context,.wy-inline-validate.wy-inline-validate-warning .wy-input-context,.wy-menu-vertical li.current>a button.toctree-expand,.wy-menu-vertical li.on a button.toctree-expand,.wy-menu-vertical li button.toctree-expand{font-family:inherit}.fa:before,.icon:before,.rst-content .admonition-title:before,.rst-content .code-block-caption .headerlink:before,.rst-content .eqno .headerlink:before,.rst-content code.download span:first-child:before,.rst-content dl dt .headerlink:before,.rst-content h1 .headerlink:before,.rst-content h2 .headerlink:before,.rst-content h3 .headerlink:before,.rst-content h4 .headerlink:before,.rst-content h5 .headerlink:before,.rst-content h6 .headerlink:before,.rst-content p.caption .headerlink:before,.rst-content p .headerlink:before,.rst-content table>caption .headerlink:before,.rst-content tt.download span:first-child:before,.wy-dropdown .caret:before,.wy-inline-validate.wy-inline-validate-danger .wy-input-context:before,.wy-inline-validate.wy-inline-validate-info .wy-input-context:before,.wy-inline-validate.wy-inline-validate-success .wy-input-context:before,.wy-inline-validate.wy-inline-validate-warning .wy-input-context:before,.wy-menu-vertical li.current>a button.toctree-expand:before,.wy-menu-vertical li.on a button.toctree-expand:before,.wy-menu-vertical li button.toctree-expand:before{font-family:FontAwesome;display:inline-block;font-style:normal;font-weight:400;line-height:1;text-decoration:inherit}.rst-content .code-block-caption a .headerlink,.rst-content .eqno a .headerlink,.rst-content a .admonition-title,.rst-content code.download a span:first-child,.rst-content dl dt a .headerlink,.rst-content h1 a .headerlink,.rst-content h2 a .headerlink,.rst-content h3 a .headerlink,.rst-content h4 a .headerlink,.rst-content h5 a .headerlink,.rst-content h6 a .headerlink,.rst-content p.caption a .headerlink,.rst-content p a .headerlink,.rst-content table>caption a .headerlink,.rst-content tt.download a span:first-child,.wy-menu-vertical li.current>a button.toctree-expand,.wy-menu-vertical li.on a button.toctree-expand,.wy-menu-vertical li a button.toctree-expand,a .fa,a .icon,a .rst-content .admonition-title,a .rst-content .code-block-caption .headerlink,a .rst-content .eqno .headerlink,a .rst-content code.download span:first-child,a .rst-content dl dt .headerlink,a .rst-content h1 .headerlink,a .rst-content h2 .headerlink,a .rst-content h3 .headerlink,a .rst-content h4 .headerlink,a .rst-content h5 .headerlink,a .rst-content h6 .headerlink,a .rst-content p.caption .headerlink,a .rst-content p .headerlink,a .rst-content table>caption .headerlink,a .rst-content tt.download span:first-child,a .wy-menu-vertical li button.toctree-expand{display:inline-block;text-decoration:inherit}.btn .fa,.btn .icon,.btn .rst-content .admonition-title,.btn .rst-content .code-block-caption .headerlink,.btn .rst-content .eqno .headerlink,.btn .rst-content code.download span:first-child,.btn .rst-content dl dt .headerlink,.btn .rst-content h1 .headerlink,.btn .rst-content h2 .headerlink,.btn .rst-content h3 .headerlink,.btn .rst-content h4 .headerlink,.btn .rst-content h5 .headerlink,.btn .rst-content h6 .headerlink,.btn .rst-content p .headerlink,.btn .rst-content table>caption .headerlink,.btn .rst-content tt.download span:first-child,.btn .wy-menu-vertical li.current>a button.toctree-expand,.btn .wy-menu-vertical li.on a button.toctree-expand,.btn .wy-menu-vertical li button.toctree-expand,.nav .fa,.nav .icon,.nav .rst-content .admonition-title,.nav .rst-content .code-block-caption .headerlink,.nav .rst-content .eqno .headerlink,.nav .rst-content code.download span:first-child,.nav .rst-content dl dt .headerlink,.nav .rst-content h1 .headerlink,.nav .rst-content h2 .headerlink,.nav .rst-content h3 .headerlink,.nav .rst-content h4 .headerlink,.nav .rst-content h5 .headerlink,.nav .rst-content h6 .headerlink,.nav .rst-content p .headerlink,.nav .rst-content table>caption .headerlink,.nav .rst-content tt.download span:first-child,.nav .wy-menu-vertical li.current>a button.toctree-expand,.nav .wy-menu-vertical li.on a button.toctree-expand,.nav .wy-menu-vertical li button.toctree-expand,.rst-content .btn .admonition-title,.rst-content .code-block-caption .btn .headerlink,.rst-content .code-block-caption .nav .headerlink,.rst-content .eqno .btn .headerlink,.rst-content .eqno .nav .headerlink,.rst-content .nav .admonition-title,.rst-content code.download .btn span:first-child,.rst-content code.download .nav span:first-child,.rst-content dl dt .btn .headerlink,.rst-content dl dt .nav .headerlink,.rst-content h1 .btn .headerlink,.rst-content h1 .nav .headerlink,.rst-content h2 .btn .headerlink,.rst-content h2 .nav .headerlink,.rst-content h3 .btn .headerlink,.rst-content h3 .nav .headerlink,.rst-content h4 .btn .headerlink,.rst-content h4 .nav .headerlink,.rst-content h5 .btn .headerlink,.rst-content h5 .nav .headerlink,.rst-content h6 .btn .headerlink,.rst-content h6 .nav .headerlink,.rst-content p .btn .headerlink,.rst-content p .nav .headerlink,.rst-content table>caption .btn .headerlink,.rst-content table>caption .nav .headerlink,.rst-content tt.download .btn span:first-child,.rst-content tt.download .nav span:first-child,.wy-menu-vertical li .btn button.toctree-expand,.wy-menu-vertical li.current>a .btn button.toctree-expand,.wy-menu-vertical li.current>a .nav button.toctree-expand,.wy-menu-vertical li .nav button.toctree-expand,.wy-menu-vertical li.on a .btn button.toctree-expand,.wy-menu-vertical li.on a .nav button.toctree-expand{display:inline}.btn .fa-large.icon,.btn .fa.fa-large,.btn .rst-content .code-block-caption .fa-large.headerlink,.btn .rst-content .eqno .fa-large.headerlink,.btn .rst-content .fa-large.admonition-title,.btn .rst-content code.download span.fa-large:first-child,.btn .rst-content dl dt .fa-large.headerlink,.btn .rst-content h1 .fa-large.headerlink,.btn .rst-content h2 .fa-large.headerlink,.btn .rst-content h3 .fa-large.headerlink,.btn .rst-content h4 .fa-large.headerlink,.btn .rst-content h5 .fa-large.headerlink,.btn .rst-content h6 .fa-large.headerlink,.btn .rst-content p .fa-large.headerlink,.btn .rst-content table>caption .fa-large.headerlink,.btn .rst-content tt.download span.fa-large:first-child,.btn .wy-menu-vertical li button.fa-large.toctree-expand,.nav .fa-large.icon,.nav .fa.fa-large,.nav .rst-content .code-block-caption .fa-large.headerlink,.nav .rst-content .eqno .fa-large.headerlink,.nav .rst-content .fa-large.admonition-title,.nav .rst-content code.download span.fa-large:first-child,.nav .rst-content dl dt .fa-large.headerlink,.nav .rst-content h1 .fa-large.headerlink,.nav .rst-content h2 .fa-large.headerlink,.nav .rst-content h3 .fa-large.headerlink,.nav .rst-content h4 .fa-large.headerlink,.nav .rst-content h5 .fa-large.headerlink,.nav .rst-content h6 .fa-large.headerlink,.nav .rst-content p .fa-large.headerlink,.nav .rst-content table>caption .fa-large.headerlink,.nav .rst-content tt.download span.fa-large:first-child,.nav .wy-menu-vertical li button.fa-large.toctree-expand,.rst-content .btn .fa-large.admonition-title,.rst-content .code-block-caption .btn .fa-large.headerlink,.rst-content .code-block-caption .nav .fa-large.headerlink,.rst-content .eqno .btn .fa-large.headerlink,.rst-content .eqno .nav .fa-large.headerlink,.rst-content .nav .fa-large.admonition-title,.rst-content code.download .btn span.fa-large:first-child,.rst-content code.download .nav span.fa-large:first-child,.rst-content dl dt .btn .fa-large.headerlink,.rst-content dl dt .nav .fa-large.headerlink,.rst-content h1 .btn .fa-large.headerlink,.rst-content h1 .nav .fa-large.headerlink,.rst-content h2 .btn .fa-large.headerlink,.rst-content h2 .nav .fa-large.headerlink,.rst-content h3 .btn .fa-large.headerlink,.rst-content h3 .nav .fa-large.headerlink,.rst-content h4 .btn .fa-large.headerlink,.rst-content h4 .nav .fa-large.headerlink,.rst-content h5 .btn .fa-large.headerlink,.rst-content h5 .nav .fa-large.headerlink,.rst-content h6 .btn .fa-large.headerlink,.rst-content h6 .nav .fa-large.headerlink,.rst-content p .btn .fa-large.headerlink,.rst-content p .nav .fa-large.headerlink,.rst-content table>caption .btn .fa-large.headerlink,.rst-content table>caption .nav .fa-large.headerlink,.rst-content tt.download .btn span.fa-large:first-child,.rst-content tt.download .nav span.fa-large:first-child,.wy-menu-vertical li .btn button.fa-large.toctree-expand,.wy-menu-vertical li .nav button.fa-large.toctree-expand{line-height:.9em}.btn .fa-spin.icon,.btn .fa.fa-spin,.btn .rst-content .code-block-caption .fa-spin.headerlink,.btn .rst-content .eqno .fa-spin.headerlink,.btn .rst-content .fa-spin.admonition-title,.btn .rst-content code.download span.fa-spin:first-child,.btn .rst-content dl dt .fa-spin.headerlink,.btn .rst-content h1 .fa-spin.headerlink,.btn .rst-content h2 .fa-spin.headerlink,.btn .rst-content h3 .fa-spin.headerlink,.btn .rst-content h4 .fa-spin.headerlink,.btn .rst-content h5 .fa-spin.headerlink,.btn .rst-content h6 .fa-spin.headerlink,.btn .rst-content p .fa-spin.headerlink,.btn .rst-content table>caption .fa-spin.headerlink,.btn .rst-content tt.download span.fa-spin:first-child,.btn .wy-menu-vertical li button.fa-spin.toctree-expand,.nav .fa-spin.icon,.nav .fa.fa-spin,.nav .rst-content .code-block-caption .fa-spin.headerlink,.nav .rst-content .eqno .fa-spin.headerlink,.nav .rst-content .fa-spin.admonition-title,.nav .rst-content code.download span.fa-spin:first-child,.nav .rst-content dl dt .fa-spin.headerlink,.nav .rst-content h1 .fa-spin.headerlink,.nav .rst-content h2 .fa-spin.headerlink,.nav .rst-content h3 .fa-spin.headerlink,.nav .rst-content h4 .fa-spin.headerlink,.nav .rst-content h5 .fa-spin.headerlink,.nav .rst-content h6 .fa-spin.headerlink,.nav .rst-content p .fa-spin.headerlink,.nav .rst-content table>caption .fa-spin.headerlink,.nav .rst-content tt.download span.fa-spin:first-child,.nav .wy-menu-vertical li button.fa-spin.toctree-expand,.rst-content .btn .fa-spin.admonition-title,.rst-content .code-block-caption .btn .fa-spin.headerlink,.rst-content .code-block-caption .nav .fa-spin.headerlink,.rst-content .eqno .btn .fa-spin.headerlink,.rst-content .eqno .nav .fa-spin.headerlink,.rst-content .nav .fa-spin.admonition-title,.rst-content code.download .btn span.fa-spin:first-child,.rst-content code.download .nav span.fa-spin:first-child,.rst-content dl dt .btn .fa-spin.headerlink,.rst-content dl dt .nav .fa-spin.headerlink,.rst-content h1 .btn .fa-spin.headerlink,.rst-content h1 .nav .fa-spin.headerlink,.rst-content h2 .btn .fa-spin.headerlink,.rst-content h2 .nav .fa-spin.headerlink,.rst-content h3 .btn .fa-spin.headerlink,.rst-content h3 .nav .fa-spin.headerlink,.rst-content h4 .btn .fa-spin.headerlink,.rst-content h4 .nav .fa-spin.headerlink,.rst-content h5 .btn .fa-spin.headerlink,.rst-content h5 .nav .fa-spin.headerlink,.rst-content h6 .btn .fa-spin.headerlink,.rst-content h6 .nav .fa-spin.headerlink,.rst-content p .btn .fa-spin.headerlink,.rst-content p .nav .fa-spin.headerlink,.rst-content table>caption .btn .fa-spin.headerlink,.rst-content table>caption .nav .fa-spin.headerlink,.rst-content tt.download .btn span.fa-spin:first-child,.rst-content tt.download .nav span.fa-spin:first-child,.wy-menu-vertical li .btn button.fa-spin.toctree-expand,.wy-menu-vertical li .nav button.fa-spin.toctree-expand{display:inline-block}.btn.fa:before,.btn.icon:before,.rst-content .btn.admonition-title:before,.rst-content .code-block-caption .btn.headerlink:before,.rst-content .eqno .btn.headerlink:before,.rst-content code.download span.btn:first-child:before,.rst-content dl dt .btn.headerlink:before,.rst-content h1 .btn.headerlink:before,.rst-content h2 .btn.headerlink:before,.rst-content h3 .btn.headerlink:before,.rst-content h4 .btn.headerlink:before,.rst-content h5 .btn.headerlink:before,.rst-content h6 .btn.headerlink:before,.rst-content p .btn.headerlink:before,.rst-content table>caption .btn.headerlink:before,.rst-content tt.download span.btn:first-child:before,.wy-menu-vertical li button.btn.toctree-expand:before{opacity:.5;-webkit-transition:opacity .05s ease-in;-moz-transition:opacity .05s ease-in;transition:opacity .05s ease-in}.btn.fa:hover:before,.btn.icon:hover:before,.rst-content .btn.admonition-title:hover:before,.rst-content .code-block-caption .btn.headerlink:hover:before,.rst-content .eqno .btn.headerlink:hover:before,.rst-content code.download span.btn:first-child:hover:before,.rst-content dl dt .btn.headerlink:hover:before,.rst-content h1 .btn.headerlink:hover:before,.rst-content h2 .btn.headerlink:hover:before,.rst-content h3 .btn.headerlink:hover:before,.rst-content h4 .btn.headerlink:hover:before,.rst-content h5 .btn.headerlink:hover:before,.rst-content h6 .btn.headerlink:hover:before,.rst-content p .btn.headerlink:hover:before,.rst-content table>caption .btn.headerlink:hover:before,.rst-content tt.download span.btn:first-child:hover:before,.wy-menu-vertical li button.btn.toctree-expand:hover:before{opacity:1}.btn-mini .fa:before,.btn-mini .icon:before,.btn-mini .rst-content .admonition-title:before,.btn-mini .rst-content .code-block-caption .headerlink:before,.btn-mini .rst-content .eqno .headerlink:before,.btn-mini .rst-content code.download span:first-child:before,.btn-mini .rst-content dl dt .headerlink:before,.btn-mini .rst-content h1 .headerlink:before,.btn-mini .rst-content h2 .headerlink:before,.btn-mini .rst-content h3 .headerlink:before,.btn-mini .rst-content h4 .headerlink:before,.btn-mini .rst-content h5 .headerlink:before,.btn-mini .rst-content h6 .headerlink:before,.btn-mini .rst-content p .headerlink:before,.btn-mini .rst-content table>caption .headerlink:before,.btn-mini .rst-content tt.download span:first-child:before,.btn-mini .wy-menu-vertical li button.toctree-expand:before,.rst-content .btn-mini .admonition-title:before,.rst-content .code-block-caption .btn-mini .headerlink:before,.rst-content .eqno .btn-mini .headerlink:before,.rst-content code.download .btn-mini span:first-child:before,.rst-content dl dt .btn-mini .headerlink:before,.rst-content h1 .btn-mini .headerlink:before,.rst-content h2 .btn-mini .headerlink:before,.rst-content h3 .btn-mini .headerlink:before,.rst-content h4 .btn-mini .headerlink:before,.rst-content h5 .btn-mini .headerlink:before,.rst-content h6 .btn-mini .headerlink:before,.rst-content p .btn-mini .headerlink:before,.rst-content table>caption .btn-mini .headerlink:before,.rst-content tt.download .btn-mini span:first-child:before,.wy-menu-vertical li .btn-mini button.toctree-expand:before{font-size:14px;vertical-align:-15%}.rst-content .admonition,.rst-content .admonition-todo,.rst-content .attention,.rst-content .caution,.rst-content .danger,.rst-content .error,.rst-content .hint,.rst-content .important,.rst-content .note,.rst-content .seealso,.rst-content .tip,.rst-content .warning,.wy-alert{padding:12px;line-height:24px;margin-bottom:24px;background:#e7f2fa}.rst-content .admonition-title,.wy-alert-title{font-weight:700;display:block;color:#fff;background:#6ab0de;padding:6px 12px;margin:-12px -12px 12px}.rst-content .danger,.rst-content .error,.rst-content .wy-alert-danger.admonition,.rst-content .wy-alert-danger.admonition-todo,.rst-content .wy-alert-danger.attention,.rst-content .wy-alert-danger.caution,.rst-content .wy-alert-danger.hint,.rst-content .wy-alert-danger.important,.rst-content .wy-alert-danger.note,.rst-content .wy-alert-danger.seealso,.rst-content .wy-alert-danger.tip,.rst-content .wy-alert-danger.warning,.wy-alert.wy-alert-danger{background:#fdf3f2}.rst-content .danger .admonition-title,.rst-content .danger .wy-alert-title,.rst-content .error .admonition-title,.rst-content .error .wy-alert-title,.rst-content .wy-alert-danger.admonition-todo .admonition-title,.rst-content .wy-alert-danger.admonition-todo .wy-alert-title,.rst-content .wy-alert-danger.admonition .admonition-title,.rst-content .wy-alert-danger.admonition .wy-alert-title,.rst-content .wy-alert-danger.attention .admonition-title,.rst-content .wy-alert-danger.attention .wy-alert-title,.rst-content .wy-alert-danger.caution .admonition-title,.rst-content .wy-alert-danger.caution .wy-alert-title,.rst-content .wy-alert-danger.hint .admonition-title,.rst-content .wy-alert-danger.hint .wy-alert-title,.rst-content .wy-alert-danger.important .admonition-title,.rst-content .wy-alert-danger.important .wy-alert-title,.rst-content .wy-alert-danger.note .admonition-title,.rst-content .wy-alert-danger.note .wy-alert-title,.rst-content .wy-alert-danger.seealso .admonition-title,.rst-content .wy-alert-danger.seealso .wy-alert-title,.rst-content .wy-alert-danger.tip .admonition-title,.rst-content .wy-alert-danger.tip .wy-alert-title,.rst-content .wy-alert-danger.warning .admonition-title,.rst-content .wy-alert-danger.warning .wy-alert-title,.rst-content .wy-alert.wy-alert-danger .admonition-title,.wy-alert.wy-alert-danger .rst-content .admonition-title,.wy-alert.wy-alert-danger .wy-alert-title{background:#f29f97}.rst-content .admonition-todo,.rst-content .attention,.rst-content .caution,.rst-content .warning,.rst-content .wy-alert-warning.admonition,.rst-content .wy-alert-warning.danger,.rst-content .wy-alert-warning.error,.rst-content .wy-alert-warning.hint,.rst-content .wy-alert-warning.important,.rst-content .wy-alert-warning.note,.rst-content .wy-alert-warning.seealso,.rst-content .wy-alert-warning.tip,.wy-alert.wy-alert-warning{background:#ffedcc}.rst-content .admonition-todo .admonition-title,.rst-content .admonition-todo .wy-alert-title,.rst-content .attention .admonition-title,.rst-content .attention .wy-alert-title,.rst-content .caution .admonition-title,.rst-content .caution .wy-alert-title,.rst-content .warning .admonition-title,.rst-content .warning .wy-alert-title,.rst-content .wy-alert-warning.admonition .admonition-title,.rst-content .wy-alert-warning.admonition .wy-alert-title,.rst-content .wy-alert-warning.danger .admonition-title,.rst-content .wy-alert-warning.danger .wy-alert-title,.rst-content .wy-alert-warning.error .admonition-title,.rst-content .wy-alert-warning.error .wy-alert-title,.rst-content .wy-alert-warning.hint .admonition-title,.rst-content .wy-alert-warning.hint .wy-alert-title,.rst-content .wy-alert-warning.important .admonition-title,.rst-content .wy-alert-warning.important .wy-alert-title,.rst-content .wy-alert-warning.note .admonition-title,.rst-content .wy-alert-warning.note .wy-alert-title,.rst-content .wy-alert-warning.seealso .admonition-title,.rst-content .wy-alert-warning.seealso .wy-alert-title,.rst-content .wy-alert-warning.tip .admonition-title,.rst-content .wy-alert-warning.tip .wy-alert-title,.rst-content .wy-alert.wy-alert-warning .admonition-title,.wy-alert.wy-alert-warning .rst-content .admonition-title,.wy-alert.wy-alert-warning .wy-alert-title{background:#f0b37e}.rst-content .note,.rst-content .seealso,.rst-content .wy-alert-info.admonition,.rst-content .wy-alert-info.admonition-todo,.rst-content .wy-alert-info.attention,.rst-content .wy-alert-info.caution,.rst-content .wy-alert-info.danger,.rst-content .wy-alert-info.error,.rst-content .wy-alert-info.hint,.rst-content .wy-alert-info.important,.rst-content .wy-alert-info.tip,.rst-content .wy-alert-info.warning,.wy-alert.wy-alert-info{background:#e7f2fa}.rst-content .note .admonition-title,.rst-content .note .wy-alert-title,.rst-content .seealso .admonition-title,.rst-content .seealso .wy-alert-title,.rst-content .wy-alert-info.admonition-todo .admonition-title,.rst-content .wy-alert-info.admonition-todo .wy-alert-title,.rst-content .wy-alert-info.admonition .admonition-title,.rst-content .wy-alert-info.admonition .wy-alert-title,.rst-content .wy-alert-info.attention .admonition-title,.rst-content .wy-alert-info.attention .wy-alert-title,.rst-content .wy-alert-info.caution .admonition-title,.rst-content .wy-alert-info.caution .wy-alert-title,.rst-content .wy-alert-info.danger .admonition-title,.rst-content .wy-alert-info.danger .wy-alert-title,.rst-content .wy-alert-info.error .admonition-title,.rst-content .wy-alert-info.error .wy-alert-title,.rst-content .wy-alert-info.hint .admonition-title,.rst-content .wy-alert-info.hint .wy-alert-title,.rst-content .wy-alert-info.important .admonition-title,.rst-content .wy-alert-info.important .wy-alert-title,.rst-content .wy-alert-info.tip .admonition-title,.rst-content .wy-alert-info.tip .wy-alert-title,.rst-content .wy-alert-info.warning .admonition-title,.rst-content .wy-alert-info.warning .wy-alert-title,.rst-content .wy-alert.wy-alert-info .admonition-title,.wy-alert.wy-alert-info .rst-content .admonition-title,.wy-alert.wy-alert-info .wy-alert-title{background:#6ab0de}.rst-content .hint,.rst-content .important,.rst-content .tip,.rst-content .wy-alert-success.admonition,.rst-content .wy-alert-success.admonition-todo,.rst-content .wy-alert-success.attention,.rst-content .wy-alert-success.caution,.rst-content .wy-alert-success.danger,.rst-content .wy-alert-success.error,.rst-content .wy-alert-success.note,.rst-content .wy-alert-success.seealso,.rst-content .wy-alert-success.warning,.wy-alert.wy-alert-success{background:#dbfaf4}.rst-content .hint .admonition-title,.rst-content .hint .wy-alert-title,.rst-content .important .admonition-title,.rst-content .important .wy-alert-title,.rst-content .tip .admonition-title,.rst-content .tip .wy-alert-title,.rst-content .wy-alert-success.admonition-todo .admonition-title,.rst-content .wy-alert-success.admonition-todo .wy-alert-title,.rst-content .wy-alert-success.admonition .admonition-title,.rst-content .wy-alert-success.admonition .wy-alert-title,.rst-content .wy-alert-success.attention .admonition-title,.rst-content .wy-alert-success.attention .wy-alert-title,.rst-content .wy-alert-success.caution .admonition-title,.rst-content .wy-alert-success.caution .wy-alert-title,.rst-content .wy-alert-success.danger .admonition-title,.rst-content .wy-alert-success.danger .wy-alert-title,.rst-content .wy-alert-success.error .admonition-title,.rst-content .wy-alert-success.error .wy-alert-title,.rst-content .wy-alert-success.note .admonition-title,.rst-content .wy-alert-success.note .wy-alert-title,.rst-content .wy-alert-success.seealso .admonition-title,.rst-content .wy-alert-success.seealso .wy-alert-title,.rst-content .wy-alert-success.warning .admonition-title,.rst-content .wy-alert-success.warning .wy-alert-title,.rst-content .wy-alert.wy-alert-success .admonition-title,.wy-alert.wy-alert-success .rst-content .admonition-title,.wy-alert.wy-alert-success .wy-alert-title{background:#1abc9c}.rst-content .wy-alert-neutral.admonition,.rst-content .wy-alert-neutral.admonition-todo,.rst-content .wy-alert-neutral.attention,.rst-content .wy-alert-neutral.caution,.rst-content .wy-alert-neutral.danger,.rst-content .wy-alert-neutral.error,.rst-content .wy-alert-neutral.hint,.rst-content .wy-alert-neutral.important,.rst-content .wy-alert-neutral.note,.rst-content .wy-alert-neutral.seealso,.rst-content .wy-alert-neutral.tip,.rst-content .wy-alert-neutral.warning,.wy-alert.wy-alert-neutral{background:#f3f6f6}.rst-content .wy-alert-neutral.admonition-todo .admonition-title,.rst-content .wy-alert-neutral.admonition-todo .wy-alert-title,.rst-content .wy-alert-neutral.admonition .admonition-title,.rst-content .wy-alert-neutral.admonition .wy-alert-title,.rst-content .wy-alert-neutral.attention .admonition-title,.rst-content .wy-alert-neutral.attention .wy-alert-title,.rst-content .wy-alert-neutral.caution .admonition-title,.rst-content .wy-alert-neutral.caution .wy-alert-title,.rst-content .wy-alert-neutral.danger .admonition-title,.rst-content .wy-alert-neutral.danger .wy-alert-title,.rst-content .wy-alert-neutral.error .admonition-title,.rst-content .wy-alert-neutral.error .wy-alert-title,.rst-content .wy-alert-neutral.hint .admonition-title,.rst-content .wy-alert-neutral.hint .wy-alert-title,.rst-content .wy-alert-neutral.important .admonition-title,.rst-content .wy-alert-neutral.important .wy-alert-title,.rst-content .wy-alert-neutral.note .admonition-title,.rst-content .wy-alert-neutral.note .wy-alert-title,.rst-content .wy-alert-neutral.seealso .admonition-title,.rst-content .wy-alert-neutral.seealso .wy-alert-title,.rst-content .wy-alert-neutral.tip .admonition-title,.rst-content .wy-alert-neutral.tip .wy-alert-title,.rst-content .wy-alert-neutral.warning .admonition-title,.rst-content .wy-alert-neutral.warning .wy-alert-title,.rst-content .wy-alert.wy-alert-neutral .admonition-title,.wy-alert.wy-alert-neutral .rst-content .admonition-title,.wy-alert.wy-alert-neutral .wy-alert-title{color:#404040;background:#e1e4e5}.rst-content .wy-alert-neutral.admonition-todo a,.rst-content .wy-alert-neutral.admonition a,.rst-content .wy-alert-neutral.attention a,.rst-content .wy-alert-neutral.caution a,.rst-content .wy-alert-neutral.danger a,.rst-content .wy-alert-neutral.error a,.rst-content .wy-alert-neutral.hint a,.rst-content .wy-alert-neutral.important a,.rst-content .wy-alert-neutral.note a,.rst-content .wy-alert-neutral.seealso a,.rst-content .wy-alert-neutral.tip a,.rst-content .wy-alert-neutral.warning a,.wy-alert.wy-alert-neutral a{color:#2980b9}.rst-content .admonition-todo p:last-child,.rst-content .admonition p:last-child,.rst-content .attention p:last-child,.rst-content .caution p:last-child,.rst-content .danger p:last-child,.rst-content .error p:last-child,.rst-content .hint p:last-child,.rst-content .important p:last-child,.rst-content .note p:last-child,.rst-content .seealso p:last-child,.rst-content .tip p:last-child,.rst-content .warning p:last-child,.wy-alert p:last-child{margin-bottom:0}.wy-tray-container{position:fixed;bottom:0;left:0;z-index:600}.wy-tray-container li{display:block;width:300px;background:transparent;color:#fff;text-align:center;box-shadow:0 5px 5px 0 rgba(0,0,0,.1);padding:0 24px;min-width:20%;opacity:0;height:0;line-height:56px;overflow:hidden;-webkit-transition:all .3s ease-in;-moz-transition:all .3s ease-in;transition:all .3s ease-in}.wy-tray-container li.wy-tray-item-success{background:#27ae60}.wy-tray-container li.wy-tray-item-info{background:#2980b9}.wy-tray-container li.wy-tray-item-warning{background:#e67e22}.wy-tray-container li.wy-tray-item-danger{background:#e74c3c}.wy-tray-container li.on{opacity:1;height:56px}@media screen and (max-width:768px){.wy-tray-container{bottom:auto;top:0;width:100%}.wy-tray-container li{width:100%}}button{font-size:100%;margin:0;vertical-align:baseline;*vertical-align:middle;cursor:pointer;line-height:normal;-webkit-appearance:button;*overflow:visible}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}button[disabled]{cursor:default}.btn{display:inline-block;border-radius:2px;line-height:normal;white-space:nowrap;text-align:center;cursor:pointer;font-size:100%;padding:6px 12px 8px;color:#fff;border:1px solid rgba(0,0,0,.1);background-color:#27ae60;text-decoration:none;font-weight:400;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;box-shadow:inset 0 1px 2px -1px hsla(0,0%,100%,.5),inset 0 -2px 0 0 rgba(0,0,0,.1);outline-none:false;vertical-align:middle;*display:inline;zoom:1;-webkit-user-drag:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;-webkit-transition:all .1s linear;-moz-transition:all .1s linear;transition:all .1s linear}.btn-hover{background:#2e8ece;color:#fff}.btn:hover{background:#2cc36b;color:#fff}.btn:focus{background:#2cc36b;outline:0}.btn:active{box-shadow:inset 0 -1px 0 0 rgba(0,0,0,.05),inset 0 2px 0 0 rgba(0,0,0,.1);padding:8px 12px 6px}.btn:visited{color:#fff}.btn-disabled,.btn-disabled:active,.btn-disabled:focus,.btn-disabled:hover,.btn:disabled{background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);filter:alpha(opacity=40);opacity:.4;cursor:not-allowed;box-shadow:none}.btn::-moz-focus-inner{padding:0;border:0}.btn-small{font-size:80%}.btn-info{background-color:#2980b9!important}.btn-info:hover{background-color:#2e8ece!important}.btn-neutral{background-color:#f3f6f6!important;color:#404040!important}.btn-neutral:hover{background-color:#e5ebeb!important;color:#404040}.btn-neutral:visited{color:#404040!important}.btn-success{background-color:#27ae60!important}.btn-success:hover{background-color:#295!important}.btn-danger{background-color:#e74c3c!important}.btn-danger:hover{background-color:#ea6153!important}.btn-warning{background-color:#e67e22!important}.btn-warning:hover{background-color:#e98b39!important}.btn-invert{background-color:#222}.btn-invert:hover{background-color:#2f2f2f!important}.btn-link{background-color:transparent!important;color:#2980b9;box-shadow:none;border-color:transparent!important}.btn-link:active,.btn-link:hover{background-color:transparent!important;color:#409ad5!important;box-shadow:none}.btn-link:visited{color:#9b59b6}.wy-btn-group .btn,.wy-control .btn{vertical-align:middle}.wy-btn-group{margin-bottom:24px;*zoom:1}.wy-btn-group:after,.wy-btn-group:before{display:table;content:""}.wy-btn-group:after{clear:both}.wy-dropdown{position:relative;display:inline-block}.wy-dropdown-active .wy-dropdown-menu{display:block}.wy-dropdown-menu{position:absolute;left:0;display:none;float:left;top:100%;min-width:100%;background:#fcfcfc;z-index:100;border:1px solid #cfd7dd;box-shadow:0 2px 2px 0 rgba(0,0,0,.1);padding:12px}.wy-dropdown-menu>dd>a{display:block;clear:both;color:#404040;white-space:nowrap;font-size:90%;padding:0 12px;cursor:pointer}.wy-dropdown-menu>dd>a:hover{background:#2980b9;color:#fff}.wy-dropdown-menu>dd.divider{border-top:1px solid #cfd7dd;margin:6px 0}.wy-dropdown-menu>dd.search{padding-bottom:12px}.wy-dropdown-menu>dd.search input[type=search]{width:100%}.wy-dropdown-menu>dd.call-to-action{background:#e3e3e3;text-transform:uppercase;font-weight:500;font-size:80%}.wy-dropdown-menu>dd.call-to-action:hover{background:#e3e3e3}.wy-dropdown-menu>dd.call-to-action .btn{color:#fff}.wy-dropdown.wy-dropdown-up .wy-dropdown-menu{bottom:100%;top:auto;left:auto;right:0}.wy-dropdown.wy-dropdown-bubble .wy-dropdown-menu{background:#fcfcfc;margin-top:2px}.wy-dropdown.wy-dropdown-bubble .wy-dropdown-menu a{padding:6px 12px}.wy-dropdown.wy-dropdown-bubble .wy-dropdown-menu a:hover{background:#2980b9;color:#fff}.wy-dropdown.wy-dropdown-left .wy-dropdown-menu{right:0;left:auto;text-align:right}.wy-dropdown-arrow:before{content:" ";border-bottom:5px solid #f5f5f5;border-left:5px solid transparent;border-right:5px solid transparent;position:absolute;display:block;top:-4px;left:50%;margin-left:-3px}.wy-dropdown-arrow.wy-dropdown-arrow-left:before{left:11px}.wy-form-stacked select{display:block}.wy-form-aligned .wy-help-inline,.wy-form-aligned input,.wy-form-aligned label,.wy-form-aligned select,.wy-form-aligned textarea{display:inline-block;*display:inline;*zoom:1;vertical-align:middle}.wy-form-aligned .wy-control-group>label{display:inline-block;vertical-align:middle;width:10em;margin:6px 12px 0 0;float:left}.wy-form-aligned .wy-control{float:left}.wy-form-aligned .wy-control label{display:block}.wy-form-aligned .wy-control select{margin-top:6px}fieldset{margin:0}fieldset,legend{border:0;padding:0}legend{width:100%;white-space:normal;margin-bottom:24px;font-size:150%;*margin-left:-7px}label,legend{display:block}label{margin:0 0 .3125em;color:#333;font-size:90%}input,select,textarea{font-size:100%;margin:0;vertical-align:baseline;*vertical-align:middle}.wy-control-group{margin-bottom:24px;max-width:1200px;margin-left:auto;margin-right:auto;*zoom:1}.wy-control-group:after,.wy-control-group:before{display:table;content:""}.wy-control-group:after{clear:both}.wy-control-group.wy-control-group-required>label:after{content:" *";color:#e74c3c}.wy-control-group .wy-form-full,.wy-control-group .wy-form-halves,.wy-control-group .wy-form-thirds{padding-bottom:12px}.wy-control-group .wy-form-full input[type=color],.wy-control-group .wy-form-full input[type=date],.wy-control-group .wy-form-full input[type=datetime-local],.wy-control-group .wy-form-full input[type=datetime],.wy-control-group .wy-form-full input[type=email],.wy-control-group .wy-form-full input[type=month],.wy-control-group .wy-form-full input[type=number],.wy-control-group .wy-form-full input[type=password],.wy-control-group .wy-form-full input[type=search],.wy-control-group .wy-form-full input[type=tel],.wy-control-group .wy-form-full input[type=text],.wy-control-group .wy-form-full input[type=time],.wy-control-group .wy-form-full input[type=url],.wy-control-group .wy-form-full input[type=week],.wy-control-group .wy-form-full select,.wy-control-group .wy-form-halves input[type=color],.wy-control-group .wy-form-halves input[type=date],.wy-control-group .wy-form-halves input[type=datetime-local],.wy-control-group .wy-form-halves input[type=datetime],.wy-control-group .wy-form-halves input[type=email],.wy-control-group .wy-form-halves input[type=month],.wy-control-group .wy-form-halves input[type=number],.wy-control-group .wy-form-halves input[type=password],.wy-control-group .wy-form-halves input[type=search],.wy-control-group .wy-form-halves input[type=tel],.wy-control-group .wy-form-halves input[type=text],.wy-control-group .wy-form-halves input[type=time],.wy-control-group .wy-form-halves input[type=url],.wy-control-group .wy-form-halves input[type=week],.wy-control-group .wy-form-halves select,.wy-control-group .wy-form-thirds input[type=color],.wy-control-group .wy-form-thirds input[type=date],.wy-control-group .wy-form-thirds input[type=datetime-local],.wy-control-group .wy-form-thirds input[type=datetime],.wy-control-group .wy-form-thirds input[type=email],.wy-control-group .wy-form-thirds input[type=month],.wy-control-group .wy-form-thirds input[type=number],.wy-control-group .wy-form-thirds input[type=password],.wy-control-group .wy-form-thirds input[type=search],.wy-control-group .wy-form-thirds input[type=tel],.wy-control-group .wy-form-thirds input[type=text],.wy-control-group .wy-form-thirds input[type=time],.wy-control-group .wy-form-thirds input[type=url],.wy-control-group .wy-form-thirds input[type=week],.wy-control-group .wy-form-thirds select{width:100%}.wy-control-group .wy-form-full{float:left;display:block;width:100%;margin-right:0}.wy-control-group .wy-form-full:last-child{margin-right:0}.wy-control-group .wy-form-halves{float:left;display:block;margin-right:2.35765%;width:48.82117%}.wy-control-group .wy-form-halves:last-child,.wy-control-group .wy-form-halves:nth-of-type(2n){margin-right:0}.wy-control-group .wy-form-halves:nth-of-type(odd){clear:left}.wy-control-group .wy-form-thirds{float:left;display:block;margin-right:2.35765%;width:31.76157%}.wy-control-group .wy-form-thirds:last-child,.wy-control-group .wy-form-thirds:nth-of-type(3n){margin-right:0}.wy-control-group .wy-form-thirds:nth-of-type(3n+1){clear:left}.wy-control-group.wy-control-group-no-input .wy-control,.wy-control-no-input{margin:6px 0 0;font-size:90%}.wy-control-no-input{display:inline-block}.wy-control-group.fluid-input input[type=color],.wy-control-group.fluid-input input[type=date],.wy-control-group.fluid-input input[type=datetime-local],.wy-control-group.fluid-input input[type=datetime],.wy-control-group.fluid-input input[type=email],.wy-control-group.fluid-input input[type=month],.wy-control-group.fluid-input input[type=number],.wy-control-group.fluid-input input[type=password],.wy-control-group.fluid-input input[type=search],.wy-control-group.fluid-input input[type=tel],.wy-control-group.fluid-input input[type=text],.wy-control-group.fluid-input input[type=time],.wy-control-group.fluid-input input[type=url],.wy-control-group.fluid-input input[type=week]{width:100%}.wy-form-message-inline{padding-left:.3em;color:#666;font-size:90%}.wy-form-message{display:block;color:#999;font-size:70%;margin-top:.3125em;font-style:italic}.wy-form-message p{font-size:inherit;font-style:italic;margin-bottom:6px}.wy-form-message p:last-child{margin-bottom:0}input{line-height:normal}input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;*overflow:visible}input[type=color],input[type=date],input[type=datetime-local],input[type=datetime],input[type=email],input[type=month],input[type=number],input[type=password],input[type=search],input[type=tel],input[type=text],input[type=time],input[type=url],input[type=week]{-webkit-appearance:none;padding:6px;display:inline-block;border:1px solid #ccc;font-size:80%;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;box-shadow:inset 0 1px 3px #ddd;border-radius:0;-webkit-transition:border .3s linear;-moz-transition:border .3s linear;transition:border .3s linear}input[type=datetime-local]{padding:.34375em .625em}input[disabled]{cursor:default}input[type=checkbox],input[type=radio]{padding:0;margin-right:.3125em;*height:13px;*width:13px}input[type=checkbox],input[type=radio],input[type=search]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}input[type=color]:focus,input[type=date]:focus,input[type=datetime-local]:focus,input[type=datetime]:focus,input[type=email]:focus,input[type=month]:focus,input[type=number]:focus,input[type=password]:focus,input[type=search]:focus,input[type=tel]:focus,input[type=text]:focus,input[type=time]:focus,input[type=url]:focus,input[type=week]:focus{outline:0;outline:thin dotted\9;border-color:#333}input.no-focus:focus{border-color:#ccc!important}input[type=checkbox]:focus,input[type=file]:focus,input[type=radio]:focus{outline:thin dotted #333;outline:1px auto #129fea}input[type=color][disabled],input[type=date][disabled],input[type=datetime-local][disabled],input[type=datetime][disabled],input[type=email][disabled],input[type=month][disabled],input[type=number][disabled],input[type=password][disabled],input[type=search][disabled],input[type=tel][disabled],input[type=text][disabled],input[type=time][disabled],input[type=url][disabled],input[type=week][disabled]{cursor:not-allowed;background-color:#fafafa}input:focus:invalid,select:focus:invalid,textarea:focus:invalid{color:#e74c3c;border:1px solid #e74c3c}input:focus:invalid:focus,select:focus:invalid:focus,textarea:focus:invalid:focus{border-color:#e74c3c}input[type=checkbox]:focus:invalid:focus,input[type=file]:focus:invalid:focus,input[type=radio]:focus:invalid:focus{outline-color:#e74c3c}input.wy-input-large{padding:12px;font-size:100%}textarea{overflow:auto;vertical-align:top;width:100%;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif}select,textarea{padding:.5em .625em;display:inline-block;border:1px solid #ccc;font-size:80%;box-shadow:inset 0 1px 3px #ddd;-webkit-transition:border .3s linear;-moz-transition:border .3s linear;transition:border .3s linear}select{border:1px solid #ccc;background-color:#fff}select[multiple]{height:auto}select:focus,textarea:focus{outline:0}input[readonly],select[disabled],select[readonly],textarea[disabled],textarea[readonly]{cursor:not-allowed;background-color:#fafafa}input[type=checkbox][disabled],input[type=radio][disabled]{cursor:not-allowed}.wy-checkbox,.wy-radio{margin:6px 0;color:#404040;display:block}.wy-checkbox input,.wy-radio input{vertical-align:baseline}.wy-form-message-inline{display:inline-block;*display:inline;*zoom:1;vertical-align:middle}.wy-input-prefix,.wy-input-suffix{white-space:nowrap;padding:6px}.wy-input-prefix .wy-input-context,.wy-input-suffix .wy-input-context{line-height:27px;padding:0 8px;display:inline-block;font-size:80%;background-color:#f3f6f6;border:1px solid #ccc;color:#999}.wy-input-suffix .wy-input-context{border-left:0}.wy-input-prefix .wy-input-context{border-right:0}.wy-switch{position:relative;display:block;height:24px;margin-top:12px;cursor:pointer}.wy-switch:before{left:0;top:0;width:36px;height:12px;background:#ccc}.wy-switch:after,.wy-switch:before{position:absolute;content:"";display:block;border-radius:4px;-webkit-transition:all .2s ease-in-out;-moz-transition:all .2s ease-in-out;transition:all .2s ease-in-out}.wy-switch:after{width:18px;height:18px;background:#999;left:-3px;top:-3px}.wy-switch span{position:absolute;left:48px;display:block;font-size:12px;color:#ccc;line-height:1}.wy-switch.active:before{background:#1e8449}.wy-switch.active:after{left:24px;background:#27ae60}.wy-switch.disabled{cursor:not-allowed;opacity:.8}.wy-control-group.wy-control-group-error .wy-form-message,.wy-control-group.wy-control-group-error>label{color:#e74c3c}.wy-control-group.wy-control-group-error input[type=color],.wy-control-group.wy-control-group-error input[type=date],.wy-control-group.wy-control-group-error input[type=datetime-local],.wy-control-group.wy-control-group-error input[type=datetime],.wy-control-group.wy-control-group-error input[type=email],.wy-control-group.wy-control-group-error input[type=month],.wy-control-group.wy-control-group-error input[type=number],.wy-control-group.wy-control-group-error input[type=password],.wy-control-group.wy-control-group-error input[type=search],.wy-control-group.wy-control-group-error input[type=tel],.wy-control-group.wy-control-group-error input[type=text],.wy-control-group.wy-control-group-error input[type=time],.wy-control-group.wy-control-group-error input[type=url],.wy-control-group.wy-control-group-error input[type=week],.wy-control-group.wy-control-group-error textarea{border:1px solid #e74c3c}.wy-inline-validate{white-space:nowrap}.wy-inline-validate .wy-input-context{padding:.5em .625em;display:inline-block;font-size:80%}.wy-inline-validate.wy-inline-validate-success .wy-input-context{color:#27ae60}.wy-inline-validate.wy-inline-validate-danger .wy-input-context{color:#e74c3c}.wy-inline-validate.wy-inline-validate-warning .wy-input-context{color:#e67e22}.wy-inline-validate.wy-inline-validate-info .wy-input-context{color:#2980b9}.rotate-90{-webkit-transform:rotate(90deg);-moz-transform:rotate(90deg);-ms-transform:rotate(90deg);-o-transform:rotate(90deg);transform:rotate(90deg)}.rotate-180{-webkit-transform:rotate(180deg);-moz-transform:rotate(180deg);-ms-transform:rotate(180deg);-o-transform:rotate(180deg);transform:rotate(180deg)}.rotate-270{-webkit-transform:rotate(270deg);-moz-transform:rotate(270deg);-ms-transform:rotate(270deg);-o-transform:rotate(270deg);transform:rotate(270deg)}.mirror{-webkit-transform:scaleX(-1);-moz-transform:scaleX(-1);-ms-transform:scaleX(-1);-o-transform:scaleX(-1);transform:scaleX(-1)}.mirror.rotate-90{-webkit-transform:scaleX(-1) rotate(90deg);-moz-transform:scaleX(-1) rotate(90deg);-ms-transform:scaleX(-1) rotate(90deg);-o-transform:scaleX(-1) rotate(90deg);transform:scaleX(-1) rotate(90deg)}.mirror.rotate-180{-webkit-transform:scaleX(-1) rotate(180deg);-moz-transform:scaleX(-1) rotate(180deg);-ms-transform:scaleX(-1) rotate(180deg);-o-transform:scaleX(-1) rotate(180deg);transform:scaleX(-1) rotate(180deg)}.mirror.rotate-270{-webkit-transform:scaleX(-1) rotate(270deg);-moz-transform:scaleX(-1) rotate(270deg);-ms-transform:scaleX(-1) rotate(270deg);-o-transform:scaleX(-1) rotate(270deg);transform:scaleX(-1) rotate(270deg)}@media only screen and (max-width:480px){.wy-form button[type=submit]{margin:.7em 0 0}.wy-form input[type=color],.wy-form input[type=date],.wy-form input[type=datetime-local],.wy-form input[type=datetime],.wy-form input[type=email],.wy-form input[type=month],.wy-form input[type=number],.wy-form input[type=password],.wy-form input[type=search],.wy-form input[type=tel],.wy-form input[type=text],.wy-form input[type=time],.wy-form input[type=url],.wy-form input[type=week],.wy-form label{margin-bottom:.3em;display:block}.wy-form input[type=color],.wy-form input[type=date],.wy-form input[type=datetime-local],.wy-form input[type=datetime],.wy-form input[type=email],.wy-form input[type=month],.wy-form input[type=number],.wy-form input[type=password],.wy-form input[type=search],.wy-form input[type=tel],.wy-form input[type=time],.wy-form input[type=url],.wy-form input[type=week]{margin-bottom:0}.wy-form-aligned .wy-control-group label{margin-bottom:.3em;text-align:left;display:block;width:100%}.wy-form-aligned .wy-control{margin:1.5em 0 0}.wy-form-message,.wy-form-message-inline,.wy-form .wy-help-inline{display:block;font-size:80%;padding:6px 0}}@media screen and (max-width:768px){.tablet-hide{display:none}}@media screen and (max-width:480px){.mobile-hide{display:none}}.float-left{float:left}.float-right{float:right}.full-width{width:100%}.rst-content table.docutils,.rst-content table.field-list,.wy-table{border-collapse:collapse;border-spacing:0;empty-cells:show;margin-bottom:24px}.rst-content table.docutils caption,.rst-content table.field-list caption,.wy-table caption{color:#000;font:italic 85%/1 arial,sans-serif;padding:1em 0;text-align:center}.rst-content table.docutils td,.rst-content table.docutils th,.rst-content table.field-list td,.rst-content table.field-list th,.wy-table td,.wy-table th{font-size:90%;margin:0;overflow:visible;padding:8px 16px}.rst-content table.docutils td:first-child,.rst-content table.docutils th:first-child,.rst-content table.field-list td:first-child,.rst-content table.field-list th:first-child,.wy-table td:first-child,.wy-table th:first-child{border-left-width:0}.rst-content table.docutils thead,.rst-content table.field-list thead,.wy-table thead{color:#000;text-align:left;vertical-align:bottom;white-space:nowrap}.rst-content table.docutils thead th,.rst-content table.field-list thead th,.wy-table thead th{font-weight:700;border-bottom:2px solid #e1e4e5}.rst-content table.docutils td,.rst-content table.field-list td,.wy-table td{background-color:transparent;vertical-align:middle}.rst-content table.docutils td p,.rst-content table.field-list td p,.wy-table td p{line-height:18px}.rst-content table.docutils td p:last-child,.rst-content table.field-list td p:last-child,.wy-table td p:last-child{margin-bottom:0}.rst-content table.docutils .wy-table-cell-min,.rst-content table.field-list .wy-table-cell-min,.wy-table .wy-table-cell-min{width:1%;padding-right:0}.rst-content table.docutils .wy-table-cell-min input[type=checkbox],.rst-content table.field-list .wy-table-cell-min input[type=checkbox],.wy-table .wy-table-cell-min input[type=checkbox]{margin:0}.wy-table-secondary{color:grey;font-size:90%}.wy-table-tertiary{color:grey;font-size:80%}.rst-content table.docutils:not(.field-list) tr:nth-child(2n-1) td,.wy-table-backed,.wy-table-odd td,.wy-table-striped tr:nth-child(2n-1) td{background-color:#f3f6f6}.rst-content table.docutils,.wy-table-bordered-all{border:1px solid #e1e4e5}.rst-content table.docutils td,.wy-table-bordered-all td{border-bottom:1px solid #e1e4e5;border-left:1px solid #e1e4e5}.rst-content table.docutils tbody>tr:last-child td,.wy-table-bordered-all tbody>tr:last-child td{border-bottom-width:0}.wy-table-bordered{border:1px solid #e1e4e5}.wy-table-bordered-rows td{border-bottom:1px solid #e1e4e5}.wy-table-bordered-rows tbody>tr:last-child td{border-bottom-width:0}.wy-table-horizontal td,.wy-table-horizontal th{border-width:0 0 1px;border-bottom:1px solid #e1e4e5}.wy-table-horizontal tbody>tr:last-child td{border-bottom-width:0}.wy-table-responsive{margin-bottom:24px;max-width:100%;overflow:auto}.wy-table-responsive table{margin-bottom:0!important}.wy-table-responsive table td,.wy-table-responsive table th{white-space:nowrap}a{color:#2980b9;text-decoration:none;cursor:pointer}a:hover{color:#3091d1}a:visited{color:#9b59b6}html{height:100%}body,html{overflow-x:hidden}body{font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;font-weight:400;color:#404040;min-height:100%;background:#edf0f2}.wy-text-left{text-align:left}.wy-text-center{text-align:center}.wy-text-right{text-align:right}.wy-text-large{font-size:120%}.wy-text-normal{font-size:100%}.wy-text-small,small{font-size:80%}.wy-text-strike{text-decoration:line-through}.wy-text-warning{color:#e67e22!important}a.wy-text-warning:hover{color:#eb9950!important}.wy-text-info{color:#2980b9!important}a.wy-text-info:hover{color:#409ad5!important}.wy-text-success{color:#27ae60!important}a.wy-text-success:hover{color:#36d278!important}.wy-text-danger{color:#e74c3c!important}a.wy-text-danger:hover{color:#ed7669!important}.wy-text-neutral{color:#404040!important}a.wy-text-neutral:hover{color:#595959!important}.rst-content .toctree-wrapper>p.caption,h1,h2,h3,h4,h5,h6,legend{margin-top:0;font-weight:700;font-family:Roboto Slab,ff-tisa-web-pro,Georgia,Arial,sans-serif}p{line-height:24px;font-size:16px;margin:0 0 24px}h1{font-size:175%}.rst-content .toctree-wrapper>p.caption,h2{font-size:150%}h3{font-size:125%}h4{font-size:115%}h5{font-size:110%}h6{font-size:100%}hr{display:block;height:1px;border:0;border-top:1px solid #e1e4e5;margin:24px 0;padding:0}.rst-content code,.rst-content tt,code{white-space:nowrap;max-width:100%;background:#fff;border:1px solid #e1e4e5;font-size:75%;padding:0 5px;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;color:#e74c3c;overflow-x:auto}.rst-content tt.code-large,code.code-large{font-size:90%}.rst-content .section ul,.rst-content .toctree-wrapper ul,.rst-content section ul,.wy-plain-list-disc,article ul{list-style:disc;line-height:24px;margin-bottom:24px}.rst-content .section ul li,.rst-content .toctree-wrapper ul li,.rst-content section ul li,.wy-plain-list-disc li,article ul li{list-style:disc;margin-left:24px}.rst-content .section ul li p:last-child,.rst-content .section ul li ul,.rst-content .toctree-wrapper ul li p:last-child,.rst-content .toctree-wrapper ul li ul,.rst-content section ul li p:last-child,.rst-content section ul li ul,.wy-plain-list-disc li p:last-child,.wy-plain-list-disc li ul,article ul li p:last-child,article ul li ul{margin-bottom:0}.rst-content .section ul li li,.rst-content .toctree-wrapper ul li li,.rst-content section ul li li,.wy-plain-list-disc li li,article ul li li{list-style:circle}.rst-content .section ul li li li,.rst-content .toctree-wrapper ul li li li,.rst-content section ul li li li,.wy-plain-list-disc li li li,article ul li li li{list-style:square}.rst-content .section ul li ol li,.rst-content .toctree-wrapper ul li ol li,.rst-content section ul li ol li,.wy-plain-list-disc li ol li,article ul li ol li{list-style:decimal}.rst-content .section ol,.rst-content .section ol.arabic,.rst-content .toctree-wrapper ol,.rst-content .toctree-wrapper ol.arabic,.rst-content section ol,.rst-content section ol.arabic,.wy-plain-list-decimal,article ol{list-style:decimal;line-height:24px;margin-bottom:24px}.rst-content .section ol.arabic li,.rst-content .section ol li,.rst-content .toctree-wrapper ol.arabic li,.rst-content .toctree-wrapper ol li,.rst-content section ol.arabic li,.rst-content section ol li,.wy-plain-list-decimal li,article ol li{list-style:decimal;margin-left:24px}.rst-content .section ol.arabic li ul,.rst-content .section ol li p:last-child,.rst-content .section ol li ul,.rst-content .toctree-wrapper ol.arabic li ul,.rst-content .toctree-wrapper ol li p:last-child,.rst-content .toctree-wrapper ol li ul,.rst-content section ol.arabic li ul,.rst-content section ol li p:last-child,.rst-content section ol li ul,.wy-plain-list-decimal li p:last-child,.wy-plain-list-decimal li ul,article ol li p:last-child,article ol li ul{margin-bottom:0}.rst-content .section ol.arabic li ul li,.rst-content .section ol li ul li,.rst-content .toctree-wrapper ol.arabic li ul li,.rst-content .toctree-wrapper ol li ul li,.rst-content section ol.arabic li ul li,.rst-content section ol li ul li,.wy-plain-list-decimal li ul li,article ol li ul li{list-style:disc}.wy-breadcrumbs{*zoom:1}.wy-breadcrumbs:after,.wy-breadcrumbs:before{display:table;content:""}.wy-breadcrumbs:after{clear:both}.wy-breadcrumbs>li{display:inline-block;padding-top:5px}.wy-breadcrumbs>li.wy-breadcrumbs-aside{float:right}.rst-content .wy-breadcrumbs>li code,.rst-content .wy-breadcrumbs>li tt,.wy-breadcrumbs>li .rst-content tt,.wy-breadcrumbs>li code{all:inherit;color:inherit}.breadcrumb-item:before{content:"/";color:#bbb;font-size:13px;padding:0 6px 0 3px}.wy-breadcrumbs-extra{margin-bottom:0;color:#b3b3b3;font-size:80%;display:inline-block}@media screen and (max-width:480px){.wy-breadcrumbs-extra,.wy-breadcrumbs li.wy-breadcrumbs-aside{display:none}}@media print{.wy-breadcrumbs li.wy-breadcrumbs-aside{display:none}}html{font-size:16px}.wy-affix{position:fixed;top:1.618em}.wy-menu a:hover{text-decoration:none}.wy-menu-horiz{*zoom:1}.wy-menu-horiz:after,.wy-menu-horiz:before{display:table;content:""}.wy-menu-horiz:after{clear:both}.wy-menu-horiz li,.wy-menu-horiz ul{display:inline-block}.wy-menu-horiz li:hover{background:hsla(0,0%,100%,.1)}.wy-menu-horiz li.divide-left{border-left:1px solid #404040}.wy-menu-horiz li.divide-right{border-right:1px solid #404040}.wy-menu-horiz a{height:32px;display:inline-block;line-height:32px;padding:0 16px}.wy-menu-vertical{width:300px}.wy-menu-vertical header,.wy-menu-vertical p.caption{color:#55a5d9;height:32px;line-height:32px;padding:0 1.618em;margin:12px 0 0;display:block;font-weight:700;text-transform:uppercase;font-size:85%;white-space:nowrap}.wy-menu-vertical ul{margin-bottom:0}.wy-menu-vertical li.divide-top{border-top:1px solid #404040}.wy-menu-vertical li.divide-bottom{border-bottom:1px solid #404040}.wy-menu-vertical li.current{background:#e3e3e3}.wy-menu-vertical li.current a{color:grey;border-right:1px solid #c9c9c9;padding:.4045em 2.427em}.wy-menu-vertical li.current a:hover{background:#d6d6d6}.rst-content .wy-menu-vertical li tt,.wy-menu-vertical li .rst-content tt,.wy-menu-vertical li code{border:none;background:inherit;color:inherit;padding-left:0;padding-right:0}.wy-menu-vertical li button.toctree-expand{display:block;float:left;margin-left:-1.2em;line-height:18px;color:#4d4d4d;border:none;background:none;padding:0}.wy-menu-vertical li.current>a,.wy-menu-vertical li.on a{color:#404040;font-weight:700;position:relative;background:#fcfcfc;border:none;padding:.4045em 1.618em}.wy-menu-vertical li.current>a:hover,.wy-menu-vertical li.on a:hover{background:#fcfcfc}.wy-menu-vertical li.current>a:hover button.toctree-expand,.wy-menu-vertical li.on a:hover button.toctree-expand{color:grey}.wy-menu-vertical li.current>a button.toctree-expand,.wy-menu-vertical li.on a button.toctree-expand{display:block;line-height:18px;color:#333}.wy-menu-vertical li.toctree-l1.current>a{border-bottom:1px solid #c9c9c9;border-top:1px solid #c9c9c9}.wy-menu-vertical .toctree-l1.current .toctree-l2>ul,.wy-menu-vertical .toctree-l2.current .toctree-l3>ul,.wy-menu-vertical .toctree-l3.current .toctree-l4>ul,.wy-menu-vertical .toctree-l4.current .toctree-l5>ul,.wy-menu-vertical .toctree-l5.current .toctree-l6>ul,.wy-menu-vertical .toctree-l6.current .toctree-l7>ul,.wy-menu-vertical .toctree-l7.current .toctree-l8>ul,.wy-menu-vertical .toctree-l8.current .toctree-l9>ul,.wy-menu-vertical .toctree-l9.current .toctree-l10>ul,.wy-menu-vertical .toctree-l10.current .toctree-l11>ul{display:none}.wy-menu-vertical .toctree-l1.current .current.toctree-l2>ul,.wy-menu-vertical .toctree-l2.current .current.toctree-l3>ul,.wy-menu-vertical .toctree-l3.current .current.toctree-l4>ul,.wy-menu-vertical .toctree-l4.current .current.toctree-l5>ul,.wy-menu-vertical .toctree-l5.current .current.toctree-l6>ul,.wy-menu-vertical .toctree-l6.current .current.toctree-l7>ul,.wy-menu-vertical .toctree-l7.current .current.toctree-l8>ul,.wy-menu-vertical .toctree-l8.current .current.toctree-l9>ul,.wy-menu-vertical .toctree-l9.current .current.toctree-l10>ul,.wy-menu-vertical .toctree-l10.current .current.toctree-l11>ul{display:block}.wy-menu-vertical li.toctree-l3,.wy-menu-vertical li.toctree-l4{font-size:.9em}.wy-menu-vertical li.toctree-l2 a,.wy-menu-vertical li.toctree-l3 a,.wy-menu-vertical li.toctree-l4 a,.wy-menu-vertical li.toctree-l5 a,.wy-menu-vertical li.toctree-l6 a,.wy-menu-vertical li.toctree-l7 a,.wy-menu-vertical li.toctree-l8 a,.wy-menu-vertical li.toctree-l9 a,.wy-menu-vertical li.toctree-l10 a{color:#404040}.wy-menu-vertical li.toctree-l2 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l3 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l4 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l5 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l6 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l7 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l8 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l9 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l10 a:hover button.toctree-expand{color:grey}.wy-menu-vertical li.toctree-l2.current li.toctree-l3>a,.wy-menu-vertical li.toctree-l3.current li.toctree-l4>a,.wy-menu-vertical li.toctree-l4.current li.toctree-l5>a,.wy-menu-vertical li.toctree-l5.current li.toctree-l6>a,.wy-menu-vertical li.toctree-l6.current li.toctree-l7>a,.wy-menu-vertical li.toctree-l7.current li.toctree-l8>a,.wy-menu-vertical li.toctree-l8.current li.toctree-l9>a,.wy-menu-vertical li.toctree-l9.current li.toctree-l10>a,.wy-menu-vertical li.toctree-l10.current li.toctree-l11>a{display:block}.wy-menu-vertical li.toctree-l2.current>a{padding:.4045em 2.427em}.wy-menu-vertical li.toctree-l2.current li.toctree-l3>a{padding:.4045em 1.618em .4045em 4.045em}.wy-menu-vertical li.toctree-l3.current>a{padding:.4045em 4.045em}.wy-menu-vertical li.toctree-l3.current li.toctree-l4>a{padding:.4045em 1.618em .4045em 5.663em}.wy-menu-vertical li.toctree-l4.current>a{padding:.4045em 5.663em}.wy-menu-vertical li.toctree-l4.current li.toctree-l5>a{padding:.4045em 1.618em .4045em 7.281em}.wy-menu-vertical li.toctree-l5.current>a{padding:.4045em 7.281em}.wy-menu-vertical li.toctree-l5.current li.toctree-l6>a{padding:.4045em 1.618em .4045em 8.899em}.wy-menu-vertical li.toctree-l6.current>a{padding:.4045em 8.899em}.wy-menu-vertical li.toctree-l6.current li.toctree-l7>a{padding:.4045em 1.618em .4045em 10.517em}.wy-menu-vertical li.toctree-l7.current>a{padding:.4045em 10.517em}.wy-menu-vertical li.toctree-l7.current li.toctree-l8>a{padding:.4045em 1.618em .4045em 12.135em}.wy-menu-vertical li.toctree-l8.current>a{padding:.4045em 12.135em}.wy-menu-vertical li.toctree-l8.current li.toctree-l9>a{padding:.4045em 1.618em .4045em 13.753em}.wy-menu-vertical li.toctree-l9.current>a{padding:.4045em 13.753em}.wy-menu-vertical li.toctree-l9.current li.toctree-l10>a{padding:.4045em 1.618em .4045em 15.371em}.wy-menu-vertical li.toctree-l10.current>a{padding:.4045em 15.371em}.wy-menu-vertical li.toctree-l10.current li.toctree-l11>a{padding:.4045em 1.618em .4045em 16.989em}.wy-menu-vertical li.toctree-l2.current>a,.wy-menu-vertical li.toctree-l2.current li.toctree-l3>a{background:#c9c9c9}.wy-menu-vertical li.toctree-l2 button.toctree-expand{color:#a3a3a3}.wy-menu-vertical li.toctree-l3.current>a,.wy-menu-vertical li.toctree-l3.current li.toctree-l4>a{background:#bdbdbd}.wy-menu-vertical li.toctree-l3 button.toctree-expand{color:#969696}.wy-menu-vertical li.current ul{display:block}.wy-menu-vertical li ul{margin-bottom:0;display:none}.wy-menu-vertical li ul li a{margin-bottom:0;color:#d9d9d9;font-weight:400}.wy-menu-vertical a{line-height:18px;padding:.4045em 1.618em;display:block;position:relative;font-size:90%;color:#d9d9d9}.wy-menu-vertical a:hover{background-color:#4e4a4a;cursor:pointer}.wy-menu-vertical a:hover button.toctree-expand{color:#d9d9d9}.wy-menu-vertical a:active{background-color:#2980b9;cursor:pointer;color:#fff}.wy-menu-vertical a:active button.toctree-expand{color:#fff}.wy-side-nav-search{display:block;width:300px;padding:.809em;margin-bottom:.809em;z-index:200;background-color:#2980b9;text-align:center;color:#fcfcfc}.wy-side-nav-search input[type=text]{width:100%;border-radius:50px;padding:6px 12px;border-color:#2472a4}.wy-side-nav-search img{display:block;margin:auto auto .809em;height:45px;width:45px;background-color:#2980b9;padding:5px;border-radius:100%}.wy-side-nav-search .wy-dropdown>a,.wy-side-nav-search>a{color:#fcfcfc;font-size:100%;font-weight:700;display:inline-block;padding:4px 6px;margin-bottom:.809em;max-width:100%}.wy-side-nav-search .wy-dropdown>a:hover,.wy-side-nav-search>a:hover{background:hsla(0,0%,100%,.1)}.wy-side-nav-search .wy-dropdown>a img.logo,.wy-side-nav-search>a img.logo{display:block;margin:0 auto;height:auto;width:auto;border-radius:0;max-width:100%;background:transparent}.wy-side-nav-search .wy-dropdown>a.icon img.logo,.wy-side-nav-search>a.icon img.logo{margin-top:.85em}.wy-side-nav-search>div.version{margin-top:-.4045em;margin-bottom:.809em;font-weight:400;color:hsla(0,0%,100%,.3)}.wy-nav .wy-menu-vertical header{color:#2980b9}.wy-nav .wy-menu-vertical a{color:#b3b3b3}.wy-nav .wy-menu-vertical a:hover{background-color:#2980b9;color:#fff}[data-menu-wrap]{-webkit-transition:all .2s ease-in;-moz-transition:all .2s ease-in;transition:all .2s ease-in;position:absolute;opacity:1;width:100%;opacity:0}[data-menu-wrap].move-center{left:0;right:auto;opacity:1}[data-menu-wrap].move-left{right:auto;left:-100%;opacity:0}[data-menu-wrap].move-right{right:-100%;left:auto;opacity:0}.wy-body-for-nav{background:#fcfcfc}.wy-grid-for-nav{position:absolute;width:100%;height:100%}.wy-nav-side{position:fixed;top:0;bottom:0;left:0;padding-bottom:2em;width:300px;overflow-x:hidden;overflow-y:hidden;min-height:100%;color:#9b9b9b;background:#343131;z-index:200}.wy-side-scroll{width:320px;position:relative;overflow-x:hidden;overflow-y:scroll;height:100%}.wy-nav-top{display:none;background:#2980b9;color:#fff;padding:.4045em .809em;position:relative;line-height:50px;text-align:center;font-size:100%;*zoom:1}.wy-nav-top:after,.wy-nav-top:before{display:table;content:""}.wy-nav-top:after{clear:both}.wy-nav-top a{color:#fff;font-weight:700}.wy-nav-top img{margin-right:12px;height:45px;width:45px;background-color:#2980b9;padding:5px;border-radius:100%}.wy-nav-top i{font-size:30px;float:left;cursor:pointer;padding-top:inherit}.wy-nav-content-wrap{margin-left:300px;background:#fcfcfc;min-height:100%}.wy-nav-content{padding:1.618em 3.236em;height:100%;max-width:800px;margin:auto}.wy-body-mask{position:fixed;width:100%;height:100%;background:rgba(0,0,0,.2);display:none;z-index:499}.wy-body-mask.on{display:block}footer{color:grey}footer p{margin-bottom:12px}.rst-content footer span.commit tt,footer span.commit .rst-content tt,footer span.commit code{padding:0;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;font-size:1em;background:none;border:none;color:grey}.rst-footer-buttons{*zoom:1}.rst-footer-buttons:after,.rst-footer-buttons:before{width:100%;display:table;content:""}.rst-footer-buttons:after{clear:both}.rst-breadcrumbs-buttons{margin-top:12px;*zoom:1}.rst-breadcrumbs-buttons:after,.rst-breadcrumbs-buttons:before{display:table;content:""}.rst-breadcrumbs-buttons:after{clear:both}#search-results .search li{margin-bottom:24px;border-bottom:1px solid #e1e4e5;padding-bottom:24px}#search-results .search li:first-child{border-top:1px solid #e1e4e5;padding-top:24px}#search-results .search li a{font-size:120%;margin-bottom:12px;display:inline-block}#search-results .context{color:grey;font-size:90%}.genindextable li>ul{margin-left:24px}@media screen and (max-width:768px){.wy-body-for-nav{background:#fcfcfc}.wy-nav-top{display:block}.wy-nav-side{left:-300px}.wy-nav-side.shift{width:85%;left:0}.wy-menu.wy-menu-vertical,.wy-side-nav-search,.wy-side-scroll{width:auto}.wy-nav-content-wrap{margin-left:0}.wy-nav-content-wrap .wy-nav-content{padding:1.618em}.wy-nav-content-wrap.shift{position:fixed;min-width:100%;left:85%;top:0;height:100%;overflow:hidden}}@media screen and (min-width:1100px){.wy-nav-content-wrap{background:rgba(0,0,0,.05)}.wy-nav-content{margin:0;background:#fcfcfc}}@media print{.rst-versions,.wy-nav-side,footer{display:none}.wy-nav-content-wrap{margin-left:0}}.rst-versions{position:fixed;bottom:0;left:0;width:300px;color:#fcfcfc;background:#1f1d1d;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;z-index:400}.rst-versions a{color:#2980b9;text-decoration:none}.rst-versions .rst-badge-small{display:none}.rst-versions .rst-current-version{padding:12px;background-color:#272525;display:block;text-align:right;font-size:90%;cursor:pointer;color:#27ae60;*zoom:1}.rst-versions .rst-current-version:after,.rst-versions .rst-current-version:before{display:table;content:""}.rst-versions .rst-current-version:after{clear:both}.rst-content .code-block-caption .rst-versions .rst-current-version .headerlink,.rst-content .eqno .rst-versions .rst-current-version .headerlink,.rst-content .rst-versions .rst-current-version .admonition-title,.rst-content code.download .rst-versions .rst-current-version span:first-child,.rst-content dl dt .rst-versions .rst-current-version .headerlink,.rst-content h1 .rst-versions .rst-current-version .headerlink,.rst-content h2 .rst-versions .rst-current-version .headerlink,.rst-content h3 .rst-versions .rst-current-version .headerlink,.rst-content h4 .rst-versions .rst-current-version .headerlink,.rst-content h5 .rst-versions .rst-current-version .headerlink,.rst-content h6 .rst-versions .rst-current-version .headerlink,.rst-content p .rst-versions .rst-current-version .headerlink,.rst-content table>caption .rst-versions .rst-current-version .headerlink,.rst-content tt.download .rst-versions .rst-current-version span:first-child,.rst-versions .rst-current-version .fa,.rst-versions .rst-current-version .icon,.rst-versions .rst-current-version .rst-content .admonition-title,.rst-versions .rst-current-version .rst-content .code-block-caption .headerlink,.rst-versions .rst-current-version .rst-content .eqno .headerlink,.rst-versions .rst-current-version .rst-content code.download span:first-child,.rst-versions .rst-current-version .rst-content dl dt .headerlink,.rst-versions .rst-current-version .rst-content h1 .headerlink,.rst-versions .rst-current-version .rst-content h2 .headerlink,.rst-versions .rst-current-version .rst-content h3 .headerlink,.rst-versions .rst-current-version .rst-content h4 .headerlink,.rst-versions .rst-current-version .rst-content h5 .headerlink,.rst-versions .rst-current-version .rst-content h6 .headerlink,.rst-versions .rst-current-version .rst-content p .headerlink,.rst-versions .rst-current-version .rst-content table>caption .headerlink,.rst-versions .rst-current-version .rst-content tt.download span:first-child,.rst-versions .rst-current-version .wy-menu-vertical li button.toctree-expand,.wy-menu-vertical li .rst-versions .rst-current-version button.toctree-expand{color:#fcfcfc}.rst-versions .rst-current-version .fa-book,.rst-versions .rst-current-version .icon-book{float:left}.rst-versions .rst-current-version.rst-out-of-date{background-color:#e74c3c;color:#fff}.rst-versions .rst-current-version.rst-active-old-version{background-color:#f1c40f;color:#000}.rst-versions.shift-up{height:auto;max-height:100%;overflow-y:scroll}.rst-versions.shift-up .rst-other-versions{display:block}.rst-versions .rst-other-versions{font-size:90%;padding:12px;color:grey;display:none}.rst-versions .rst-other-versions hr{display:block;height:1px;border:0;margin:20px 0;padding:0;border-top:1px solid #413d3d}.rst-versions .rst-other-versions dd{display:inline-block;margin:0}.rst-versions .rst-other-versions dd a{display:inline-block;padding:6px;color:#fcfcfc}.rst-versions.rst-badge{width:auto;bottom:20px;right:20px;left:auto;border:none;max-width:300px;max-height:90%}.rst-versions.rst-badge .fa-book,.rst-versions.rst-badge .icon-book{float:none;line-height:30px}.rst-versions.rst-badge.shift-up .rst-current-version{text-align:right}.rst-versions.rst-badge.shift-up .rst-current-version .fa-book,.rst-versions.rst-badge.shift-up .rst-current-version .icon-book{float:left}.rst-versions.rst-badge>.rst-current-version{width:auto;height:30px;line-height:30px;padding:0 6px;display:block;text-align:center}@media screen and (max-width:768px){.rst-versions{width:85%;display:none}.rst-versions.shift{display:block}}.rst-content .toctree-wrapper>p.caption,.rst-content h1,.rst-content h2,.rst-content h3,.rst-content h4,.rst-content h5,.rst-content h6{margin-bottom:24px}.rst-content img{max-width:100%;height:auto}.rst-content div.figure,.rst-content figure{margin-bottom:24px}.rst-content div.figure .caption-text,.rst-content figure .caption-text{font-style:italic}.rst-content div.figure p:last-child.caption,.rst-content figure p:last-child.caption{margin-bottom:0}.rst-content div.figure.align-center,.rst-content figure.align-center{text-align:center}.rst-content .section>a>img,.rst-content .section>img,.rst-content section>a>img,.rst-content section>img{margin-bottom:24px}.rst-content abbr[title]{text-decoration:none}.rst-content.style-external-links a.reference.external:after{font-family:FontAwesome;content:"\f08e";color:#b3b3b3;vertical-align:super;font-size:60%;margin:0 .2em}.rst-content blockquote{margin-left:24px;line-height:24px;margin-bottom:24px}.rst-content pre.literal-block{white-space:pre;margin:0;padding:12px;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;display:block;overflow:auto}.rst-content div[class^=highlight],.rst-content pre.literal-block{border:1px solid #e1e4e5;overflow-x:auto;margin:1px 0 24px}.rst-content div[class^=highlight] div[class^=highlight],.rst-content pre.literal-block div[class^=highlight]{padding:0;border:none;margin:0}.rst-content div[class^=highlight] td.code{width:100%}.rst-content .linenodiv pre{border-right:1px solid #e6e9ea;margin:0;padding:12px;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;user-select:none;pointer-events:none}.rst-content div[class^=highlight] pre{white-space:pre;margin:0;padding:12px;display:block;overflow:auto}.rst-content div[class^=highlight] pre .hll{display:block;margin:0 -12px;padding:0 12px}.rst-content .linenodiv pre,.rst-content div[class^=highlight] pre,.rst-content pre.literal-block{font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;font-size:12px;line-height:1.4}.rst-content div.highlight .gp,.rst-content div.highlight span.linenos{user-select:none;pointer-events:none}.rst-content div.highlight span.linenos{display:inline-block;padding-left:0;padding-right:12px;margin-right:12px;border-right:1px solid #e6e9ea}.rst-content .code-block-caption{font-style:italic;font-size:85%;line-height:1;padding:1em 0;text-align:center}@media print{.rst-content .codeblock,.rst-content div[class^=highlight],.rst-content div[class^=highlight] pre{white-space:pre-wrap}}.rst-content .admonition,.rst-content .admonition-todo,.rst-content .attention,.rst-content .caution,.rst-content .danger,.rst-content .error,.rst-content .hint,.rst-content .important,.rst-content .note,.rst-content .seealso,.rst-content .tip,.rst-content .warning{clear:both}.rst-content .admonition-todo .last,.rst-content .admonition-todo>:last-child,.rst-content .admonition .last,.rst-content .admonition>:last-child,.rst-content .attention .last,.rst-content .attention>:last-child,.rst-content .caution .last,.rst-content .caution>:last-child,.rst-content .danger .last,.rst-content .danger>:last-child,.rst-content .error .last,.rst-content .error>:last-child,.rst-content .hint .last,.rst-content .hint>:last-child,.rst-content .important .last,.rst-content .important>:last-child,.rst-content .note .last,.rst-content .note>:last-child,.rst-content .seealso .last,.rst-content .seealso>:last-child,.rst-content .tip .last,.rst-content .tip>:last-child,.rst-content .warning .last,.rst-content .warning>:last-child{margin-bottom:0}.rst-content .admonition-title:before{margin-right:4px}.rst-content .admonition table{border-color:rgba(0,0,0,.1)}.rst-content .admonition table td,.rst-content .admonition table th{background:transparent!important;border-color:rgba(0,0,0,.1)!important}.rst-content .section ol.loweralpha,.rst-content .section ol.loweralpha>li,.rst-content .toctree-wrapper ol.loweralpha,.rst-content .toctree-wrapper ol.loweralpha>li,.rst-content section ol.loweralpha,.rst-content section ol.loweralpha>li{list-style:lower-alpha}.rst-content .section ol.upperalpha,.rst-content .section ol.upperalpha>li,.rst-content .toctree-wrapper ol.upperalpha,.rst-content .toctree-wrapper ol.upperalpha>li,.rst-content section ol.upperalpha,.rst-content section ol.upperalpha>li{list-style:upper-alpha}.rst-content .section ol li>*,.rst-content .section ul li>*,.rst-content .toctree-wrapper ol li>*,.rst-content .toctree-wrapper ul li>*,.rst-content section ol li>*,.rst-content section ul li>*{margin-top:12px;margin-bottom:12px}.rst-content .section ol li>:first-child,.rst-content .section ul li>:first-child,.rst-content .toctree-wrapper ol li>:first-child,.rst-content .toctree-wrapper ul li>:first-child,.rst-content section ol li>:first-child,.rst-content section ul li>:first-child{margin-top:0}.rst-content .section ol li>p,.rst-content .section ol li>p:last-child,.rst-content .section ul li>p,.rst-content .section ul li>p:last-child,.rst-content .toctree-wrapper ol li>p,.rst-content .toctree-wrapper ol li>p:last-child,.rst-content .toctree-wrapper ul li>p,.rst-content .toctree-wrapper ul li>p:last-child,.rst-content section ol li>p,.rst-content section ol li>p:last-child,.rst-content section ul li>p,.rst-content section ul li>p:last-child{margin-bottom:12px}.rst-content .section ol li>p:only-child,.rst-content .section ol li>p:only-child:last-child,.rst-content .section ul li>p:only-child,.rst-content .section ul li>p:only-child:last-child,.rst-content .toctree-wrapper ol li>p:only-child,.rst-content .toctree-wrapper ol li>p:only-child:last-child,.rst-content .toctree-wrapper ul li>p:only-child,.rst-content .toctree-wrapper ul li>p:only-child:last-child,.rst-content section ol li>p:only-child,.rst-content section ol li>p:only-child:last-child,.rst-content section ul li>p:only-child,.rst-content section ul li>p:only-child:last-child{margin-bottom:0}.rst-content .section ol li>ol,.rst-content .section ol li>ul,.rst-content .section ul li>ol,.rst-content .section ul li>ul,.rst-content .toctree-wrapper ol li>ol,.rst-content .toctree-wrapper ol li>ul,.rst-content .toctree-wrapper ul li>ol,.rst-content .toctree-wrapper ul li>ul,.rst-content section ol li>ol,.rst-content section ol li>ul,.rst-content section ul li>ol,.rst-content section ul li>ul{margin-bottom:12px}.rst-content .section ol.simple li>*,.rst-content .section ol.simple li ol,.rst-content .section ol.simple li ul,.rst-content .section ul.simple li>*,.rst-content .section ul.simple li ol,.rst-content .section ul.simple li ul,.rst-content .toctree-wrapper ol.simple li>*,.rst-content .toctree-wrapper ol.simple li ol,.rst-content .toctree-wrapper ol.simple li ul,.rst-content .toctree-wrapper ul.simple li>*,.rst-content .toctree-wrapper ul.simple li ol,.rst-content .toctree-wrapper ul.simple li ul,.rst-content section ol.simple li>*,.rst-content section ol.simple li ol,.rst-content section ol.simple li ul,.rst-content section ul.simple li>*,.rst-content section ul.simple li ol,.rst-content section ul.simple li ul{margin-top:0;margin-bottom:0}.rst-content .line-block{margin-left:0;margin-bottom:24px;line-height:24px}.rst-content .line-block .line-block{margin-left:24px;margin-bottom:0}.rst-content .topic-title{font-weight:700;margin-bottom:12px}.rst-content .toc-backref{color:#404040}.rst-content .align-right{float:right;margin:0 0 24px 24px}.rst-content .align-left{float:left;margin:0 24px 24px 0}.rst-content .align-center{margin:auto}.rst-content .align-center:not(table){display:block}.rst-content .code-block-caption .headerlink,.rst-content .eqno .headerlink,.rst-content .toctree-wrapper>p.caption .headerlink,.rst-content dl dt .headerlink,.rst-content h1 .headerlink,.rst-content h2 .headerlink,.rst-content h3 .headerlink,.rst-content h4 .headerlink,.rst-content h5 .headerlink,.rst-content h6 .headerlink,.rst-content p.caption .headerlink,.rst-content p .headerlink,.rst-content table>caption .headerlink{opacity:0;font-size:14px;font-family:FontAwesome;margin-left:.5em}.rst-content .code-block-caption .headerlink:focus,.rst-content .code-block-caption:hover .headerlink,.rst-content .eqno .headerlink:focus,.rst-content .eqno:hover .headerlink,.rst-content .toctree-wrapper>p.caption .headerlink:focus,.rst-content .toctree-wrapper>p.caption:hover .headerlink,.rst-content dl dt .headerlink:focus,.rst-content dl dt:hover .headerlink,.rst-content h1 .headerlink:focus,.rst-content h1:hover .headerlink,.rst-content h2 .headerlink:focus,.rst-content h2:hover .headerlink,.rst-content h3 .headerlink:focus,.rst-content h3:hover .headerlink,.rst-content h4 .headerlink:focus,.rst-content h4:hover .headerlink,.rst-content h5 .headerlink:focus,.rst-content h5:hover .headerlink,.rst-content h6 .headerlink:focus,.rst-content h6:hover .headerlink,.rst-content p.caption .headerlink:focus,.rst-content p.caption:hover .headerlink,.rst-content p .headerlink:focus,.rst-content p:hover .headerlink,.rst-content table>caption .headerlink:focus,.rst-content table>caption:hover .headerlink{opacity:1}.rst-content p a{overflow-wrap:anywhere}.rst-content .wy-table td p,.rst-content .wy-table td ul,.rst-content .wy-table th p,.rst-content .wy-table th ul,.rst-content table.docutils td p,.rst-content table.docutils td ul,.rst-content table.docutils th p,.rst-content table.docutils th ul,.rst-content table.field-list td p,.rst-content table.field-list td ul,.rst-content table.field-list th p,.rst-content table.field-list th ul{font-size:inherit}.rst-content .btn:focus{outline:2px solid}.rst-content table>caption .headerlink:after{font-size:12px}.rst-content .centered{text-align:center}.rst-content .sidebar{float:right;width:40%;display:block;margin:0 0 24px 24px;padding:24px;background:#f3f6f6;border:1px solid #e1e4e5}.rst-content .sidebar dl,.rst-content .sidebar p,.rst-content .sidebar ul{font-size:90%}.rst-content .sidebar .last,.rst-content .sidebar>:last-child{margin-bottom:0}.rst-content .sidebar .sidebar-title{display:block;font-family:Roboto Slab,ff-tisa-web-pro,Georgia,Arial,sans-serif;font-weight:700;background:#e1e4e5;padding:6px 12px;margin:-24px -24px 24px;font-size:100%}.rst-content .highlighted{background:#f1c40f;box-shadow:0 0 0 2px #f1c40f;display:inline;font-weight:700}.rst-content .citation-reference,.rst-content .footnote-reference{vertical-align:baseline;position:relative;top:-.4em;line-height:0;font-size:90%}.rst-content .citation-reference>span.fn-bracket,.rst-content .footnote-reference>span.fn-bracket{display:none}.rst-content .hlist{width:100%}.rst-content dl dt span.classifier:before{content:" : "}.rst-content dl dt span.classifier-delimiter{display:none!important}html.writer-html4 .rst-content table.docutils.citation,html.writer-html4 .rst-content table.docutils.footnote{background:none;border:none}html.writer-html4 .rst-content table.docutils.citation td,html.writer-html4 .rst-content table.docutils.citation tr,html.writer-html4 .rst-content table.docutils.footnote td,html.writer-html4 .rst-content table.docutils.footnote tr{border:none;background-color:transparent!important;white-space:normal}html.writer-html4 .rst-content table.docutils.citation td.label,html.writer-html4 .rst-content table.docutils.footnote td.label{padding-left:0;padding-right:0;vertical-align:top}html.writer-html5 .rst-content dl.citation,html.writer-html5 .rst-content dl.field-list,html.writer-html5 .rst-content dl.footnote{display:grid;grid-template-columns:auto minmax(80%,95%)}html.writer-html5 .rst-content dl.citation>dt,html.writer-html5 .rst-content dl.field-list>dt,html.writer-html5 .rst-content dl.footnote>dt{display:inline-grid;grid-template-columns:max-content auto}html.writer-html5 .rst-content aside.citation,html.writer-html5 .rst-content aside.footnote,html.writer-html5 .rst-content div.citation{display:grid;grid-template-columns:auto auto minmax(.65rem,auto) minmax(40%,95%)}html.writer-html5 .rst-content aside.citation>span.label,html.writer-html5 .rst-content aside.footnote>span.label,html.writer-html5 .rst-content div.citation>span.label{grid-column-start:1;grid-column-end:2}html.writer-html5 .rst-content aside.citation>span.backrefs,html.writer-html5 .rst-content aside.footnote>span.backrefs,html.writer-html5 .rst-content div.citation>span.backrefs{grid-column-start:2;grid-column-end:3;grid-row-start:1;grid-row-end:3}html.writer-html5 .rst-content aside.citation>p,html.writer-html5 .rst-content aside.footnote>p,html.writer-html5 .rst-content div.citation>p{grid-column-start:4;grid-column-end:5}html.writer-html5 .rst-content dl.citation,html.writer-html5 .rst-content dl.field-list,html.writer-html5 .rst-content dl.footnote{margin-bottom:24px}html.writer-html5 .rst-content dl.citation>dt,html.writer-html5 .rst-content dl.field-list>dt,html.writer-html5 .rst-content dl.footnote>dt{padding-left:1rem}html.writer-html5 .rst-content dl.citation>dd,html.writer-html5 .rst-content dl.citation>dt,html.writer-html5 .rst-content dl.field-list>dd,html.writer-html5 .rst-content dl.field-list>dt,html.writer-html5 .rst-content dl.footnote>dd,html.writer-html5 .rst-content dl.footnote>dt{margin-bottom:0}html.writer-html5 .rst-content dl.citation,html.writer-html5 .rst-content dl.footnote{font-size:.9rem}html.writer-html5 .rst-content dl.citation>dt,html.writer-html5 .rst-content dl.footnote>dt{margin:0 .5rem .5rem 0;line-height:1.2rem;word-break:break-all;font-weight:400}html.writer-html5 .rst-content dl.citation>dt>span.brackets:before,html.writer-html5 .rst-content dl.footnote>dt>span.brackets:before{content:"["}html.writer-html5 .rst-content dl.citation>dt>span.brackets:after,html.writer-html5 .rst-content dl.footnote>dt>span.brackets:after{content:"]"}html.writer-html5 .rst-content dl.citation>dt>span.fn-backref,html.writer-html5 .rst-content dl.footnote>dt>span.fn-backref{text-align:left;font-style:italic;margin-left:.65rem;word-break:break-word;word-spacing:-.1rem;max-width:5rem}html.writer-html5 .rst-content dl.citation>dt>span.fn-backref>a,html.writer-html5 .rst-content dl.footnote>dt>span.fn-backref>a{word-break:keep-all}html.writer-html5 .rst-content dl.citation>dt>span.fn-backref>a:not(:first-child):before,html.writer-html5 .rst-content dl.footnote>dt>span.fn-backref>a:not(:first-child):before{content:" "}html.writer-html5 .rst-content dl.citation>dd,html.writer-html5 .rst-content dl.footnote>dd{margin:0 0 .5rem;line-height:1.2rem}html.writer-html5 .rst-content dl.citation>dd p,html.writer-html5 .rst-content dl.footnote>dd p{font-size:.9rem}html.writer-html5 .rst-content aside.citation,html.writer-html5 .rst-content aside.footnote,html.writer-html5 .rst-content div.citation{padding-left:1rem;padding-right:1rem;font-size:.9rem;line-height:1.2rem}html.writer-html5 .rst-content aside.citation p,html.writer-html5 .rst-content aside.footnote p,html.writer-html5 .rst-content div.citation p{font-size:.9rem;line-height:1.2rem;margin-bottom:12px}html.writer-html5 .rst-content aside.citation span.backrefs,html.writer-html5 .rst-content aside.footnote span.backrefs,html.writer-html5 .rst-content div.citation span.backrefs{text-align:left;font-style:italic;margin-left:.65rem;word-break:break-word;word-spacing:-.1rem;max-width:5rem}html.writer-html5 .rst-content aside.citation span.backrefs>a,html.writer-html5 .rst-content aside.footnote span.backrefs>a,html.writer-html5 .rst-content div.citation span.backrefs>a{word-break:keep-all}html.writer-html5 .rst-content aside.citation span.backrefs>a:not(:first-child):before,html.writer-html5 .rst-content aside.footnote span.backrefs>a:not(:first-child):before,html.writer-html5 .rst-content div.citation span.backrefs>a:not(:first-child):before{content:" "}html.writer-html5 .rst-content aside.citation span.label,html.writer-html5 .rst-content aside.footnote span.label,html.writer-html5 .rst-content div.citation span.label{line-height:1.2rem}html.writer-html5 .rst-content aside.citation-list,html.writer-html5 .rst-content aside.footnote-list,html.writer-html5 .rst-content div.citation-list{margin-bottom:24px}html.writer-html5 .rst-content dl.option-list kbd{font-size:.9rem}.rst-content table.docutils.footnote,html.writer-html4 .rst-content table.docutils.citation,html.writer-html5 .rst-content aside.footnote,html.writer-html5 .rst-content aside.footnote-list aside.footnote,html.writer-html5 .rst-content div.citation-list>div.citation,html.writer-html5 .rst-content dl.citation,html.writer-html5 .rst-content dl.footnote{color:grey}.rst-content table.docutils.footnote code,.rst-content table.docutils.footnote tt,html.writer-html4 .rst-content table.docutils.citation code,html.writer-html4 .rst-content table.docutils.citation tt,html.writer-html5 .rst-content aside.footnote-list aside.footnote code,html.writer-html5 .rst-content aside.footnote-list aside.footnote tt,html.writer-html5 .rst-content aside.footnote code,html.writer-html5 .rst-content aside.footnote tt,html.writer-html5 .rst-content div.citation-list>div.citation code,html.writer-html5 .rst-content div.citation-list>div.citation tt,html.writer-html5 .rst-content dl.citation code,html.writer-html5 .rst-content dl.citation tt,html.writer-html5 .rst-content dl.footnote code,html.writer-html5 .rst-content dl.footnote tt{color:#555}.rst-content .wy-table-responsive.citation,.rst-content .wy-table-responsive.footnote{margin-bottom:0}.rst-content .wy-table-responsive.citation+:not(.citation),.rst-content .wy-table-responsive.footnote+:not(.footnote){margin-top:24px}.rst-content .wy-table-responsive.citation:last-child,.rst-content .wy-table-responsive.footnote:last-child{margin-bottom:24px}.rst-content table.docutils th{border-color:#e1e4e5}html.writer-html5 .rst-content table.docutils th{border:1px solid #e1e4e5}html.writer-html5 .rst-content table.docutils td>p,html.writer-html5 .rst-content table.docutils th>p{line-height:1rem;margin-bottom:0;font-size:.9rem}.rst-content table.docutils td .last,.rst-content table.docutils td .last>:last-child{margin-bottom:0}.rst-content table.field-list,.rst-content table.field-list td{border:none}.rst-content table.field-list td p{line-height:inherit}.rst-content table.field-list td>strong{display:inline-block}.rst-content table.field-list .field-name{padding-right:10px;text-align:left;white-space:nowrap}.rst-content table.field-list .field-body{text-align:left}.rst-content code,.rst-content tt{color:#000;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;padding:2px 5px}.rst-content code big,.rst-content code em,.rst-content tt big,.rst-content tt em{font-size:100%!important;line-height:normal}.rst-content code.literal,.rst-content tt.literal{color:#e74c3c;white-space:normal}.rst-content code.xref,.rst-content tt.xref,a .rst-content code,a .rst-content tt{font-weight:700;color:#404040;overflow-wrap:normal}.rst-content kbd,.rst-content pre,.rst-content samp{font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace}.rst-content a code,.rst-content a tt{color:#2980b9}.rst-content dl{margin-bottom:24px}.rst-content dl dt{font-weight:700;margin-bottom:12px}.rst-content dl ol,.rst-content dl p,.rst-content dl table,.rst-content dl ul{margin-bottom:12px}.rst-content dl dd{margin:0 0 12px 24px;line-height:24px}.rst-content dl dd>ol:last-child,.rst-content dl dd>p:last-child,.rst-content dl dd>table:last-child,.rst-content dl dd>ul:last-child{margin-bottom:0}html.writer-html4 .rst-content dl:not(.docutils),html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple){margin-bottom:24px}html.writer-html4 .rst-content dl:not(.docutils)>dt,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt{display:table;margin:6px 0;font-size:90%;line-height:normal;background:#e7f2fa;color:#2980b9;border-top:3px solid #6ab0de;padding:6px;position:relative}html.writer-html4 .rst-content dl:not(.docutils)>dt:before,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt:before{color:#6ab0de}html.writer-html4 .rst-content dl:not(.docutils)>dt .headerlink,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt .headerlink{color:#404040;font-size:100%!important}html.writer-html4 .rst-content dl:not(.docutils) dl:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) dl:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt{margin-bottom:6px;border:none;border-left:3px solid #ccc;background:#f0f0f0;color:#555}html.writer-html4 .rst-content dl:not(.docutils) dl:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt .headerlink,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) dl:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt .headerlink{color:#404040;font-size:100%!important}html.writer-html4 .rst-content dl:not(.docutils)>dt:first-child,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt:first-child{margin-top:0}html.writer-html4 .rst-content dl:not(.docutils) code.descclassname,html.writer-html4 .rst-content dl:not(.docutils) code.descname,html.writer-html4 .rst-content dl:not(.docutils) tt.descclassname,html.writer-html4 .rst-content dl:not(.docutils) tt.descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) code.descclassname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) code.descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) tt.descclassname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) tt.descname{background-color:transparent;border:none;padding:0;font-size:100%!important}html.writer-html4 .rst-content dl:not(.docutils) code.descname,html.writer-html4 .rst-content dl:not(.docutils) tt.descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) code.descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) tt.descname{font-weight:700}html.writer-html4 .rst-content dl:not(.docutils) .optional,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .optional{display:inline-block;padding:0 4px;color:#000;font-weight:700}html.writer-html4 .rst-content dl:not(.docutils) .property,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .property{display:inline-block;padding-right:8px;max-width:100%}html.writer-html4 .rst-content dl:not(.docutils) .k,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .k{font-style:italic}html.writer-html4 .rst-content dl:not(.docutils) .descclassname,html.writer-html4 .rst-content dl:not(.docutils) .descname,html.writer-html4 .rst-content dl:not(.docutils) .sig-name,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .descclassname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .sig-name{font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;color:#000}.rst-content .viewcode-back,.rst-content .viewcode-link{display:inline-block;color:#27ae60;font-size:80%;padding-left:24px}.rst-content .viewcode-back{display:block;float:right}.rst-content p.rubric{margin-bottom:12px;font-weight:700}.rst-content code.download,.rst-content tt.download{background:inherit;padding:inherit;font-weight:400;font-family:inherit;font-size:inherit;color:inherit;border:inherit;white-space:inherit}.rst-content code.download span:first-child,.rst-content tt.download span:first-child{-webkit-font-smoothing:subpixel-antialiased}.rst-content code.download span:first-child:before,.rst-content tt.download span:first-child:before{margin-right:4px}.rst-content .guilabel{border:1px solid #7fbbe3;background:#e7f2fa;font-size:80%;font-weight:700;border-radius:4px;padding:2.4px 6px;margin:auto 2px}.rst-content :not(dl.option-list)>:not(dt):not(kbd):not(.kbd)>.kbd,.rst-content :not(dl.option-list)>:not(dt):not(kbd):not(.kbd)>kbd{color:inherit;font-size:80%;background-color:#fff;border:1px solid #a6a6a6;border-radius:4px;box-shadow:0 2px grey;padding:2.4px 6px;margin:auto 0}.rst-content .versionmodified{font-style:italic}@media screen and (max-width:480px){.rst-content .sidebar{width:100%}}span[id*=MathJax-Span]{color:#404040}.math{text-align:center}@font-face{font-family:Lato;src:url(fonts/lato-normal.woff2?bd03a2cc277bbbc338d464e679fe9942) format("woff2"),url(fonts/lato-normal.woff?27bd77b9162d388cb8d4c4217c7c5e2a) format("woff");font-weight:400;font-style:normal;font-display:block}@font-face{font-family:Lato;src:url(fonts/lato-bold.woff2?cccb897485813c7c256901dbca54ecf2) format("woff2"),url(fonts/lato-bold.woff?d878b6c29b10beca227e9eef4246111b) format("woff");font-weight:700;font-style:normal;font-display:block}@font-face{font-family:Lato;src:url(fonts/lato-bold-italic.woff2?0b6bb6725576b072c5d0b02ecdd1900d) format("woff2"),url(fonts/lato-bold-italic.woff?9c7e4e9eb485b4a121c760e61bc3707c) format("woff");font-weight:700;font-style:italic;font-display:block}@font-face{font-family:Lato;src:url(fonts/lato-normal-italic.woff2?4eb103b4d12be57cb1d040ed5e162e9d) format("woff2"),url(fonts/lato-normal-italic.woff?f28f2d6482446544ef1ea1ccc6dd5892) format("woff");font-weight:400;font-style:italic;font-display:block}@font-face{font-family:Roboto Slab;font-style:normal;font-weight:400;src:url(fonts/Roboto-Slab-Regular.woff2?7abf5b8d04d26a2cafea937019bca958) format("woff2"),url(fonts/Roboto-Slab-Regular.woff?c1be9284088d487c5e3ff0a10a92e58c) format("woff");font-display:block}@font-face{font-family:Roboto Slab;font-style:normal;font-weight:700;src:url(fonts/Roboto-Slab-Bold.woff2?9984f4a9bda09be08e83f2506954adbe) format("woff2"),url(fonts/Roboto-Slab-Bold.woff?bed5564a116b05148e3b3bea6fb1162a) format("woff");font-display:block} diff --git a/css/theme_extra.css b/css/theme_extra.css new file mode 100644 index 0000000..ab0631a --- /dev/null +++ b/css/theme_extra.css @@ -0,0 +1,197 @@ +/* + * Wrap inline code samples otherwise they shoot of the side and + * can't be read at all. + * + * https://github.com/mkdocs/mkdocs/issues/313 + * https://github.com/mkdocs/mkdocs/issues/233 + * https://github.com/mkdocs/mkdocs/issues/834 + */ +.rst-content code { + white-space: pre-wrap; + word-wrap: break-word; + padding: 2px 5px; +} + +/** + * Make code blocks display as blocks and give them the appropriate + * font size and padding. + * + * https://github.com/mkdocs/mkdocs/issues/855 + * https://github.com/mkdocs/mkdocs/issues/834 + * https://github.com/mkdocs/mkdocs/issues/233 + */ +.rst-content pre code { + white-space: pre; + word-wrap: normal; + display: block; + padding: 12px; + font-size: 12px; +} + +/** + * Fix code colors + * + * https://github.com/mkdocs/mkdocs/issues/2027 + */ +.rst-content code { + color: #E74C3C; +} + +.rst-content pre code { + color: #000; + background: #f8f8f8; +} + +/* + * Fix link colors when the link text is inline code. + * + * https://github.com/mkdocs/mkdocs/issues/718 + */ +a code { + color: #2980B9; +} +a:hover code { + color: #3091d1; +} +a:visited code { + color: #9B59B6; +} + +/* + * The CSS classes from highlight.js seem to clash with the + * ReadTheDocs theme causing some code to be incorrectly made + * bold and italic. + * + * https://github.com/mkdocs/mkdocs/issues/411 + */ +pre .cs, pre .c { + font-weight: inherit; + font-style: inherit; +} + +/* + * Fix some issues with the theme and non-highlighted code + * samples. Without and highlighting styles attached the + * formatting is broken. + * + * https://github.com/mkdocs/mkdocs/issues/319 + */ +.rst-content .no-highlight { + display: block; + padding: 0.5em; + color: #333; +} + + +/* + * Additions specific to the search functionality provided by MkDocs + */ + +.search-results { + margin-top: 23px; +} + +.search-results article { + border-top: 1px solid #E1E4E5; + padding-top: 24px; +} + +.search-results article:first-child { + border-top: none; +} + +form .search-query { + width: 100%; + border-radius: 50px; + padding: 6px 12px; + border-color: #D1D4D5; +} + +/* + * Improve inline code blocks within admonitions. + * + * https://github.com/mkdocs/mkdocs/issues/656 + */ + .rst-content .admonition code { + color: #404040; + border: 1px solid #c7c9cb; + border: 1px solid rgba(0, 0, 0, 0.2); + background: #f8fbfd; + background: rgba(255, 255, 255, 0.7); +} + +/* + * Account for wide tables which go off the side. + * Override borders to avoid weirdness on narrow tables. + * + * https://github.com/mkdocs/mkdocs/issues/834 + * https://github.com/mkdocs/mkdocs/pull/1034 + */ +.rst-content .section .docutils { + width: 100%; + overflow: auto; + display: block; + border: none; +} + +td, th { + border: 1px solid #e1e4e5 !important; + border-collapse: collapse; +} + +/* + * Without the following amendments, the navigation in the theme will be + * slightly cut off. This is due to the fact that the .wy-nav-side has a + * padding-bottom of 2em, which must not necessarily align with the font-size of + * 90 % on the .rst-current-version container, combined with the padding of 12px + * above and below. These amendments fix this in two steps: First, make sure the + * .rst-current-version container has a fixed height of 40px, achieved using + * line-height, and then applying a padding-bottom of 40px to this container. In + * a second step, the items within that container are re-aligned using flexbox. + * + * https://github.com/mkdocs/mkdocs/issues/2012 + */ + .wy-nav-side { + padding-bottom: 40px; +} + +/* For section-index only */ +.wy-menu-vertical .current-section p { + background-color: #e3e3e3; + color: #404040; +} + +/* + * The second step of above amendment: Here we make sure the items are aligned + * correctly within the .rst-current-version container. Using flexbox, we + * achieve it in such a way that it will look like the following: + * + * [No repo_name] + * Next >> // On the first page + * << Previous Next >> // On all subsequent pages + * + * [With repo_name] + * Next >> // On the first page + * << Previous Next >> // On all subsequent pages + * + * https://github.com/mkdocs/mkdocs/issues/2012 + */ +.rst-versions .rst-current-version { + padding: 0 12px; + display: flex; + font-size: initial; + justify-content: space-between; + align-items: center; + line-height: 40px; +} + +/* + * Please note that this amendment also involves removing certain inline-styles + * from the file ./mkdocs/themes/readthedocs/versions.html. + * + * https://github.com/mkdocs/mkdocs/issues/2012 + */ +.rst-current-version span { + flex: 1; + text-align: center; +} diff --git a/img/favicon.ico b/img/favicon.ico new file mode 100644 index 0000000..e85006a Binary files /dev/null and b/img/favicon.ico differ diff --git a/index.html b/index.html new file mode 100644 index 0000000..5f293a1 --- /dev/null +++ b/index.html @@ -0,0 +1,196 @@ + + + + + + + + FNA Docs + + + + + + + + + + + + + + +
+ + +
+ +
+
+ +
+
+
+
+ +

What is FNA?

+

FNA is a reimplementation of the Microsoft XNA Game Studio 4.0 Refresh libraries.

+

FNA is primarily developed by video game porter Ethan Lee, who has shipped more than a dozen ports of XNA games using the exact branch that you see on GitHub today!

+

General information about FNA can be found on the FNA website.

+

Those looking for the original XNA 4.0 Refresh documentation can find it here.

+

How do I use FNA?

+

This wiki is designed to be a step-by-step guide to getting your game running on FNA, whether it's an existing XNA game or a brand new game entirely:

+
    +
  • The zeroth page is an FAQ, just in case.
  • +
  • The first three pages are the essential steps necessary to get any title running on FNA, regardless of any other dependencies your game might have. The second page in particular is split up for existing XNA games as well as new games.
  • +
  • The fourth and fifth pages go over common mistakes made by XNA games that obstruct the portability provided by FNA and some solutions that can quickly resolve those mistakes. The fifth page also describes features added to FNA that allow further portability and various enhancements to the user experience.
  • +
  • The sixth and seventh pages go over ways that FNA can be optimized and tuned specially for your game, as well as some additional materials related to debugging with FNA. Modders may also find these sections interesting when modifying the FNA binary provided with your game.
  • +
  • The final page is a rough guide on how you can contribute to FNA's development.
  • +
+

It is strongly suggested that you read the documentation in the order provided, and that you read the documentation in full, as every page is meant to be useful to any developer working with FNA. You might be surprised by what you can really do with this library!

+ +
+
+ +
+
+ +
+ +
+ +
+ + + + GitHub + + + + + Next » + + +
+ + + + + + + + + + + diff --git a/js/html5shiv.min.js b/js/html5shiv.min.js new file mode 100644 index 0000000..1a01c94 --- /dev/null +++ b/js/html5shiv.min.js @@ -0,0 +1,4 @@ +/** +* @preserve HTML5 Shiv 3.7.3 | @afarkas @jdalton @jon_neal @rem | MIT/GPL2 Licensed +*/ +!function(a,b){function c(a,b){var c=a.createElement("p"),d=a.getElementsByTagName("head")[0]||a.documentElement;return c.innerHTML="x",d.insertBefore(c.lastChild,d.firstChild)}function d(){var a=t.elements;return"string"==typeof a?a.split(" "):a}function e(a,b){var c=t.elements;"string"!=typeof c&&(c=c.join(" ")),"string"!=typeof a&&(a=a.join(" ")),t.elements=c+" "+a,j(b)}function f(a){var b=s[a[q]];return b||(b={},r++,a[q]=r,s[r]=b),b}function g(a,c,d){if(c||(c=b),l)return c.createElement(a);d||(d=f(c));var e;return e=d.cache[a]?d.cache[a].cloneNode():p.test(a)?(d.cache[a]=d.createElem(a)).cloneNode():d.createElem(a),!e.canHaveChildren||o.test(a)||e.tagUrn?e:d.frag.appendChild(e)}function h(a,c){if(a||(a=b),l)return a.createDocumentFragment();c=c||f(a);for(var e=c.frag.cloneNode(),g=0,h=d(),i=h.length;i>g;g++)e.createElement(h[g]);return e}function i(a,b){b.cache||(b.cache={},b.createElem=a.createElement,b.createFrag=a.createDocumentFragment,b.frag=b.createFrag()),a.createElement=function(c){return t.shivMethods?g(c,a,b):b.createElem(c)},a.createDocumentFragment=Function("h,f","return function(){var n=f.cloneNode(),c=n.createElement;h.shivMethods&&("+d().join().replace(/[\w\-:]+/g,function(a){return b.createElem(a),b.frag.createElement(a),'c("'+a+'")'})+");return n}")(t,b.frag)}function j(a){a||(a=b);var d=f(a);return!t.shivCSS||k||d.hasCSS||(d.hasCSS=!!c(a,"article,aside,dialog,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}mark{background:#FF0;color:#000}template{display:none}")),l||i(a,d),a}var k,l,m="3.7.3",n=a.html5||{},o=/^<|^(?:button|map|select|textarea|object|iframe|option|optgroup)$/i,p=/^(?:a|b|code|div|fieldset|h1|h2|h3|h4|h5|h6|i|label|li|ol|p|q|span|strong|style|table|tbody|td|th|tr|ul)$/i,q="_html5shiv",r=0,s={};!function(){try{var a=b.createElement("a");a.innerHTML="",k="hidden"in a,l=1==a.childNodes.length||function(){b.createElement("a");var a=b.createDocumentFragment();return"undefined"==typeof a.cloneNode||"undefined"==typeof a.createDocumentFragment||"undefined"==typeof a.createElement}()}catch(c){k=!0,l=!0}}();var t={elements:n.elements||"abbr article aside audio bdi canvas data datalist details dialog figcaption figure footer header hgroup main mark meter nav output picture progress section summary template time video",version:m,shivCSS:n.shivCSS!==!1,supportsUnknownElements:l,shivMethods:n.shivMethods!==!1,type:"default",shivDocument:j,createElement:g,createDocumentFragment:h,addElements:e};a.html5=t,j(b),"object"==typeof module&&module.exports&&(module.exports=t)}("undefined"!=typeof window?window:this,document); diff --git a/js/jquery-3.6.0.min.js b/js/jquery-3.6.0.min.js new file mode 100644 index 0000000..c4c6022 --- /dev/null +++ b/js/jquery-3.6.0.min.js @@ -0,0 +1,2 @@ +/*! jQuery v3.6.0 | (c) OpenJS Foundation and other contributors | jquery.org/license */ +!function(e,t){"use strict";"object"==typeof module&&"object"==typeof module.exports?module.exports=e.document?t(e,!0):function(e){if(!e.document)throw new Error("jQuery requires a window with a document");return t(e)}:t(e)}("undefined"!=typeof window?window:this,function(C,e){"use strict";var t=[],r=Object.getPrototypeOf,s=t.slice,g=t.flat?function(e){return t.flat.call(e)}:function(e){return t.concat.apply([],e)},u=t.push,i=t.indexOf,n={},o=n.toString,v=n.hasOwnProperty,a=v.toString,l=a.call(Object),y={},m=function(e){return"function"==typeof e&&"number"!=typeof e.nodeType&&"function"!=typeof e.item},x=function(e){return null!=e&&e===e.window},E=C.document,c={type:!0,src:!0,nonce:!0,noModule:!0};function b(e,t,n){var r,i,o=(n=n||E).createElement("script");if(o.text=e,t)for(r in c)(i=t[r]||t.getAttribute&&t.getAttribute(r))&&o.setAttribute(r,i);n.head.appendChild(o).parentNode.removeChild(o)}function w(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?n[o.call(e)]||"object":typeof e}var f="3.6.0",S=function(e,t){return new S.fn.init(e,t)};function p(e){var t=!!e&&"length"in e&&e.length,n=w(e);return!m(e)&&!x(e)&&("array"===n||0===t||"number"==typeof t&&0+~]|"+M+")"+M+"*"),U=new RegExp(M+"|>"),X=new RegExp(F),V=new RegExp("^"+I+"$"),G={ID:new RegExp("^#("+I+")"),CLASS:new RegExp("^\\.("+I+")"),TAG:new RegExp("^("+I+"|[*])"),ATTR:new RegExp("^"+W),PSEUDO:new RegExp("^"+F),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+M+"*(even|odd|(([+-]|)(\\d*)n|)"+M+"*(?:([+-]|)"+M+"*(\\d+)|))"+M+"*\\)|)","i"),bool:new RegExp("^(?:"+R+")$","i"),needsContext:new RegExp("^"+M+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+M+"*((?:-\\d)?\\d*)"+M+"*\\)|)(?=[^-]|$)","i")},Y=/HTML$/i,Q=/^(?:input|select|textarea|button)$/i,J=/^h\d$/i,K=/^[^{]+\{\s*\[native \w/,Z=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,ee=/[+~]/,te=new RegExp("\\\\[\\da-fA-F]{1,6}"+M+"?|\\\\([^\\r\\n\\f])","g"),ne=function(e,t){var n="0x"+e.slice(1)-65536;return t||(n<0?String.fromCharCode(n+65536):String.fromCharCode(n>>10|55296,1023&n|56320))},re=/([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g,ie=function(e,t){return t?"\0"===e?"\ufffd":e.slice(0,-1)+"\\"+e.charCodeAt(e.length-1).toString(16)+" ":"\\"+e},oe=function(){T()},ae=be(function(e){return!0===e.disabled&&"fieldset"===e.nodeName.toLowerCase()},{dir:"parentNode",next:"legend"});try{H.apply(t=O.call(p.childNodes),p.childNodes),t[p.childNodes.length].nodeType}catch(e){H={apply:t.length?function(e,t){L.apply(e,O.call(t))}:function(e,t){var n=e.length,r=0;while(e[n++]=t[r++]);e.length=n-1}}}function se(t,e,n,r){var i,o,a,s,u,l,c,f=e&&e.ownerDocument,p=e?e.nodeType:9;if(n=n||[],"string"!=typeof t||!t||1!==p&&9!==p&&11!==p)return n;if(!r&&(T(e),e=e||C,E)){if(11!==p&&(u=Z.exec(t)))if(i=u[1]){if(9===p){if(!(a=e.getElementById(i)))return n;if(a.id===i)return n.push(a),n}else if(f&&(a=f.getElementById(i))&&y(e,a)&&a.id===i)return n.push(a),n}else{if(u[2])return H.apply(n,e.getElementsByTagName(t)),n;if((i=u[3])&&d.getElementsByClassName&&e.getElementsByClassName)return H.apply(n,e.getElementsByClassName(i)),n}if(d.qsa&&!N[t+" "]&&(!v||!v.test(t))&&(1!==p||"object"!==e.nodeName.toLowerCase())){if(c=t,f=e,1===p&&(U.test(t)||z.test(t))){(f=ee.test(t)&&ye(e.parentNode)||e)===e&&d.scope||((s=e.getAttribute("id"))?s=s.replace(re,ie):e.setAttribute("id",s=S)),o=(l=h(t)).length;while(o--)l[o]=(s?"#"+s:":scope")+" "+xe(l[o]);c=l.join(",")}try{return H.apply(n,f.querySelectorAll(c)),n}catch(e){N(t,!0)}finally{s===S&&e.removeAttribute("id")}}}return g(t.replace($,"$1"),e,n,r)}function ue(){var r=[];return function e(t,n){return r.push(t+" ")>b.cacheLength&&delete e[r.shift()],e[t+" "]=n}}function le(e){return e[S]=!0,e}function ce(e){var t=C.createElement("fieldset");try{return!!e(t)}catch(e){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function fe(e,t){var n=e.split("|"),r=n.length;while(r--)b.attrHandle[n[r]]=t}function pe(e,t){var n=t&&e,r=n&&1===e.nodeType&&1===t.nodeType&&e.sourceIndex-t.sourceIndex;if(r)return r;if(n)while(n=n.nextSibling)if(n===t)return-1;return e?1:-1}function de(t){return function(e){return"input"===e.nodeName.toLowerCase()&&e.type===t}}function he(n){return function(e){var t=e.nodeName.toLowerCase();return("input"===t||"button"===t)&&e.type===n}}function ge(t){return function(e){return"form"in e?e.parentNode&&!1===e.disabled?"label"in e?"label"in e.parentNode?e.parentNode.disabled===t:e.disabled===t:e.isDisabled===t||e.isDisabled!==!t&&ae(e)===t:e.disabled===t:"label"in e&&e.disabled===t}}function ve(a){return le(function(o){return o=+o,le(function(e,t){var n,r=a([],e.length,o),i=r.length;while(i--)e[n=r[i]]&&(e[n]=!(t[n]=e[n]))})})}function ye(e){return e&&"undefined"!=typeof e.getElementsByTagName&&e}for(e in d=se.support={},i=se.isXML=function(e){var t=e&&e.namespaceURI,n=e&&(e.ownerDocument||e).documentElement;return!Y.test(t||n&&n.nodeName||"HTML")},T=se.setDocument=function(e){var t,n,r=e?e.ownerDocument||e:p;return r!=C&&9===r.nodeType&&r.documentElement&&(a=(C=r).documentElement,E=!i(C),p!=C&&(n=C.defaultView)&&n.top!==n&&(n.addEventListener?n.addEventListener("unload",oe,!1):n.attachEvent&&n.attachEvent("onunload",oe)),d.scope=ce(function(e){return a.appendChild(e).appendChild(C.createElement("div")),"undefined"!=typeof e.querySelectorAll&&!e.querySelectorAll(":scope fieldset div").length}),d.attributes=ce(function(e){return e.className="i",!e.getAttribute("className")}),d.getElementsByTagName=ce(function(e){return e.appendChild(C.createComment("")),!e.getElementsByTagName("*").length}),d.getElementsByClassName=K.test(C.getElementsByClassName),d.getById=ce(function(e){return a.appendChild(e).id=S,!C.getElementsByName||!C.getElementsByName(S).length}),d.getById?(b.filter.ID=function(e){var t=e.replace(te,ne);return function(e){return e.getAttribute("id")===t}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n=t.getElementById(e);return n?[n]:[]}}):(b.filter.ID=function(e){var n=e.replace(te,ne);return function(e){var t="undefined"!=typeof e.getAttributeNode&&e.getAttributeNode("id");return t&&t.value===n}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n,r,i,o=t.getElementById(e);if(o){if((n=o.getAttributeNode("id"))&&n.value===e)return[o];i=t.getElementsByName(e),r=0;while(o=i[r++])if((n=o.getAttributeNode("id"))&&n.value===e)return[o]}return[]}}),b.find.TAG=d.getElementsByTagName?function(e,t){return"undefined"!=typeof t.getElementsByTagName?t.getElementsByTagName(e):d.qsa?t.querySelectorAll(e):void 0}:function(e,t){var n,r=[],i=0,o=t.getElementsByTagName(e);if("*"===e){while(n=o[i++])1===n.nodeType&&r.push(n);return r}return o},b.find.CLASS=d.getElementsByClassName&&function(e,t){if("undefined"!=typeof t.getElementsByClassName&&E)return t.getElementsByClassName(e)},s=[],v=[],(d.qsa=K.test(C.querySelectorAll))&&(ce(function(e){var t;a.appendChild(e).innerHTML="",e.querySelectorAll("[msallowcapture^='']").length&&v.push("[*^$]="+M+"*(?:''|\"\")"),e.querySelectorAll("[selected]").length||v.push("\\["+M+"*(?:value|"+R+")"),e.querySelectorAll("[id~="+S+"-]").length||v.push("~="),(t=C.createElement("input")).setAttribute("name",""),e.appendChild(t),e.querySelectorAll("[name='']").length||v.push("\\["+M+"*name"+M+"*="+M+"*(?:''|\"\")"),e.querySelectorAll(":checked").length||v.push(":checked"),e.querySelectorAll("a#"+S+"+*").length||v.push(".#.+[+~]"),e.querySelectorAll("\\\f"),v.push("[\\r\\n\\f]")}),ce(function(e){e.innerHTML="";var t=C.createElement("input");t.setAttribute("type","hidden"),e.appendChild(t).setAttribute("name","D"),e.querySelectorAll("[name=d]").length&&v.push("name"+M+"*[*^$|!~]?="),2!==e.querySelectorAll(":enabled").length&&v.push(":enabled",":disabled"),a.appendChild(e).disabled=!0,2!==e.querySelectorAll(":disabled").length&&v.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),v.push(",.*:")})),(d.matchesSelector=K.test(c=a.matches||a.webkitMatchesSelector||a.mozMatchesSelector||a.oMatchesSelector||a.msMatchesSelector))&&ce(function(e){d.disconnectedMatch=c.call(e,"*"),c.call(e,"[s!='']:x"),s.push("!=",F)}),v=v.length&&new RegExp(v.join("|")),s=s.length&&new RegExp(s.join("|")),t=K.test(a.compareDocumentPosition),y=t||K.test(a.contains)?function(e,t){var n=9===e.nodeType?e.documentElement:e,r=t&&t.parentNode;return e===r||!(!r||1!==r.nodeType||!(n.contains?n.contains(r):e.compareDocumentPosition&&16&e.compareDocumentPosition(r)))}:function(e,t){if(t)while(t=t.parentNode)if(t===e)return!0;return!1},j=t?function(e,t){if(e===t)return l=!0,0;var n=!e.compareDocumentPosition-!t.compareDocumentPosition;return n||(1&(n=(e.ownerDocument||e)==(t.ownerDocument||t)?e.compareDocumentPosition(t):1)||!d.sortDetached&&t.compareDocumentPosition(e)===n?e==C||e.ownerDocument==p&&y(p,e)?-1:t==C||t.ownerDocument==p&&y(p,t)?1:u?P(u,e)-P(u,t):0:4&n?-1:1)}:function(e,t){if(e===t)return l=!0,0;var n,r=0,i=e.parentNode,o=t.parentNode,a=[e],s=[t];if(!i||!o)return e==C?-1:t==C?1:i?-1:o?1:u?P(u,e)-P(u,t):0;if(i===o)return pe(e,t);n=e;while(n=n.parentNode)a.unshift(n);n=t;while(n=n.parentNode)s.unshift(n);while(a[r]===s[r])r++;return r?pe(a[r],s[r]):a[r]==p?-1:s[r]==p?1:0}),C},se.matches=function(e,t){return se(e,null,null,t)},se.matchesSelector=function(e,t){if(T(e),d.matchesSelector&&E&&!N[t+" "]&&(!s||!s.test(t))&&(!v||!v.test(t)))try{var n=c.call(e,t);if(n||d.disconnectedMatch||e.document&&11!==e.document.nodeType)return n}catch(e){N(t,!0)}return 0":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(te,ne),e[3]=(e[3]||e[4]||e[5]||"").replace(te,ne),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||se.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&se.error(e[0]),e},PSEUDO:function(e){var t,n=!e[6]&&e[2];return G.CHILD.test(e[0])?null:(e[3]?e[2]=e[4]||e[5]||"":n&&X.test(n)&&(t=h(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(te,ne).toLowerCase();return"*"===e?function(){return!0}:function(e){return e.nodeName&&e.nodeName.toLowerCase()===t}},CLASS:function(e){var t=m[e+" "];return t||(t=new RegExp("(^|"+M+")"+e+"("+M+"|$)"))&&m(e,function(e){return t.test("string"==typeof e.className&&e.className||"undefined"!=typeof e.getAttribute&&e.getAttribute("class")||"")})},ATTR:function(n,r,i){return function(e){var t=se.attr(e,n);return null==t?"!="===r:!r||(t+="","="===r?t===i:"!="===r?t!==i:"^="===r?i&&0===t.indexOf(i):"*="===r?i&&-1:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i;function j(e,n,r){return m(n)?S.grep(e,function(e,t){return!!n.call(e,t,e)!==r}):n.nodeType?S.grep(e,function(e){return e===n!==r}):"string"!=typeof n?S.grep(e,function(e){return-1)[^>]*|#([\w-]+))$/;(S.fn.init=function(e,t,n){var r,i;if(!e)return this;if(n=n||D,"string"==typeof e){if(!(r="<"===e[0]&&">"===e[e.length-1]&&3<=e.length?[null,e,null]:q.exec(e))||!r[1]&&t)return!t||t.jquery?(t||n).find(e):this.constructor(t).find(e);if(r[1]){if(t=t instanceof S?t[0]:t,S.merge(this,S.parseHTML(r[1],t&&t.nodeType?t.ownerDocument||t:E,!0)),N.test(r[1])&&S.isPlainObject(t))for(r in t)m(this[r])?this[r](t[r]):this.attr(r,t[r]);return this}return(i=E.getElementById(r[2]))&&(this[0]=i,this.length=1),this}return e.nodeType?(this[0]=e,this.length=1,this):m(e)?void 0!==n.ready?n.ready(e):e(S):S.makeArray(e,this)}).prototype=S.fn,D=S(E);var L=/^(?:parents|prev(?:Until|All))/,H={children:!0,contents:!0,next:!0,prev:!0};function O(e,t){while((e=e[t])&&1!==e.nodeType);return e}S.fn.extend({has:function(e){var t=S(e,this),n=t.length;return this.filter(function(){for(var e=0;e\x20\t\r\n\f]*)/i,he=/^$|^module$|\/(?:java|ecma)script/i;ce=E.createDocumentFragment().appendChild(E.createElement("div")),(fe=E.createElement("input")).setAttribute("type","radio"),fe.setAttribute("checked","checked"),fe.setAttribute("name","t"),ce.appendChild(fe),y.checkClone=ce.cloneNode(!0).cloneNode(!0).lastChild.checked,ce.innerHTML="",y.noCloneChecked=!!ce.cloneNode(!0).lastChild.defaultValue,ce.innerHTML="",y.option=!!ce.lastChild;var ge={thead:[1,"","
"],col:[2,"","
"],tr:[2,"","
"],td:[3,"","
"],_default:[0,"",""]};function ve(e,t){var n;return n="undefined"!=typeof e.getElementsByTagName?e.getElementsByTagName(t||"*"):"undefined"!=typeof e.querySelectorAll?e.querySelectorAll(t||"*"):[],void 0===t||t&&A(e,t)?S.merge([e],n):n}function ye(e,t){for(var n=0,r=e.length;n",""]);var me=/<|&#?\w+;/;function xe(e,t,n,r,i){for(var o,a,s,u,l,c,f=t.createDocumentFragment(),p=[],d=0,h=e.length;d\s*$/g;function je(e,t){return A(e,"table")&&A(11!==t.nodeType?t:t.firstChild,"tr")&&S(e).children("tbody")[0]||e}function De(e){return e.type=(null!==e.getAttribute("type"))+"/"+e.type,e}function qe(e){return"true/"===(e.type||"").slice(0,5)?e.type=e.type.slice(5):e.removeAttribute("type"),e}function Le(e,t){var n,r,i,o,a,s;if(1===t.nodeType){if(Y.hasData(e)&&(s=Y.get(e).events))for(i in Y.remove(t,"handle events"),s)for(n=0,r=s[i].length;n").attr(n.scriptAttrs||{}).prop({charset:n.scriptCharset,src:n.url}).on("load error",i=function(e){r.remove(),i=null,e&&t("error"===e.type?404:200,e.type)}),E.head.appendChild(r[0])},abort:function(){i&&i()}}});var _t,zt=[],Ut=/(=)\?(?=&|$)|\?\?/;S.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var e=zt.pop()||S.expando+"_"+wt.guid++;return this[e]=!0,e}}),S.ajaxPrefilter("json jsonp",function(e,t,n){var r,i,o,a=!1!==e.jsonp&&(Ut.test(e.url)?"url":"string"==typeof e.data&&0===(e.contentType||"").indexOf("application/x-www-form-urlencoded")&&Ut.test(e.data)&&"data");if(a||"jsonp"===e.dataTypes[0])return r=e.jsonpCallback=m(e.jsonpCallback)?e.jsonpCallback():e.jsonpCallback,a?e[a]=e[a].replace(Ut,"$1"+r):!1!==e.jsonp&&(e.url+=(Tt.test(e.url)?"&":"?")+e.jsonp+"="+r),e.converters["script json"]=function(){return o||S.error(r+" was not called"),o[0]},e.dataTypes[0]="json",i=C[r],C[r]=function(){o=arguments},n.always(function(){void 0===i?S(C).removeProp(r):C[r]=i,e[r]&&(e.jsonpCallback=t.jsonpCallback,zt.push(r)),o&&m(i)&&i(o[0]),o=i=void 0}),"script"}),y.createHTMLDocument=((_t=E.implementation.createHTMLDocument("").body).innerHTML="
",2===_t.childNodes.length),S.parseHTML=function(e,t,n){return"string"!=typeof e?[]:("boolean"==typeof t&&(n=t,t=!1),t||(y.createHTMLDocument?((r=(t=E.implementation.createHTMLDocument("")).createElement("base")).href=E.location.href,t.head.appendChild(r)):t=E),o=!n&&[],(i=N.exec(e))?[t.createElement(i[1])]:(i=xe([e],t,o),o&&o.length&&S(o).remove(),S.merge([],i.childNodes)));var r,i,o},S.fn.load=function(e,t,n){var r,i,o,a=this,s=e.indexOf(" ");return-1").append(S.parseHTML(e)).find(r):e)}).always(n&&function(e,t){a.each(function(){n.apply(this,o||[e.responseText,t,e])})}),this},S.expr.pseudos.animated=function(t){return S.grep(S.timers,function(e){return t===e.elem}).length},S.offset={setOffset:function(e,t,n){var r,i,o,a,s,u,l=S.css(e,"position"),c=S(e),f={};"static"===l&&(e.style.position="relative"),s=c.offset(),o=S.css(e,"top"),u=S.css(e,"left"),("absolute"===l||"fixed"===l)&&-1<(o+u).indexOf("auto")?(a=(r=c.position()).top,i=r.left):(a=parseFloat(o)||0,i=parseFloat(u)||0),m(t)&&(t=t.call(e,n,S.extend({},s))),null!=t.top&&(f.top=t.top-s.top+a),null!=t.left&&(f.left=t.left-s.left+i),"using"in t?t.using.call(e,f):c.css(f)}},S.fn.extend({offset:function(t){if(arguments.length)return void 0===t?this:this.each(function(e){S.offset.setOffset(this,t,e)});var e,n,r=this[0];return r?r.getClientRects().length?(e=r.getBoundingClientRect(),n=r.ownerDocument.defaultView,{top:e.top+n.pageYOffset,left:e.left+n.pageXOffset}):{top:0,left:0}:void 0},position:function(){if(this[0]){var e,t,n,r=this[0],i={top:0,left:0};if("fixed"===S.css(r,"position"))t=r.getBoundingClientRect();else{t=this.offset(),n=r.ownerDocument,e=r.offsetParent||n.documentElement;while(e&&(e===n.body||e===n.documentElement)&&"static"===S.css(e,"position"))e=e.parentNode;e&&e!==r&&1===e.nodeType&&((i=S(e).offset()).top+=S.css(e,"borderTopWidth",!0),i.left+=S.css(e,"borderLeftWidth",!0))}return{top:t.top-i.top-S.css(r,"marginTop",!0),left:t.left-i.left-S.css(r,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var e=this.offsetParent;while(e&&"static"===S.css(e,"position"))e=e.offsetParent;return e||re})}}),S.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(t,i){var o="pageYOffset"===i;S.fn[t]=function(e){return $(this,function(e,t,n){var r;if(x(e)?r=e:9===e.nodeType&&(r=e.defaultView),void 0===n)return r?r[i]:e[t];r?r.scrollTo(o?r.pageXOffset:n,o?n:r.pageYOffset):e[t]=n},t,e,arguments.length)}}),S.each(["top","left"],function(e,n){S.cssHooks[n]=Fe(y.pixelPosition,function(e,t){if(t)return t=We(e,n),Pe.test(t)?S(e).position()[n]+"px":t})}),S.each({Height:"height",Width:"width"},function(a,s){S.each({padding:"inner"+a,content:s,"":"outer"+a},function(r,o){S.fn[o]=function(e,t){var n=arguments.length&&(r||"boolean"!=typeof e),i=r||(!0===e||!0===t?"margin":"border");return $(this,function(e,t,n){var r;return x(e)?0===o.indexOf("outer")?e["inner"+a]:e.document.documentElement["client"+a]:9===e.nodeType?(r=e.documentElement,Math.max(e.body["scroll"+a],r["scroll"+a],e.body["offset"+a],r["offset"+a],r["client"+a])):void 0===n?S.css(e,t,i):S.style(e,t,n,i)},s,n?e:void 0,n)}})}),S.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(e,t){S.fn[t]=function(e){return this.on(t,e)}}),S.fn.extend({bind:function(e,t,n){return this.on(e,null,t,n)},unbind:function(e,t){return this.off(e,null,t)},delegate:function(e,t,n,r){return this.on(t,e,n,r)},undelegate:function(e,t,n){return 1===arguments.length?this.off(e,"**"):this.off(t,e||"**",n)},hover:function(e,t){return this.mouseenter(e).mouseleave(t||e)}}),S.each("blur focus focusin focusout resize scroll click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup contextmenu".split(" "),function(e,n){S.fn[n]=function(e,t){return 0"),n("table.docutils.footnote").wrap("
"),n("table.docutils.citation").wrap("
"),n(".wy-menu-vertical ul").not(".simple").siblings("a").each((function(){var t=n(this);expand=n(''),expand.on("click",(function(n){return e.toggleCurrent(t),n.stopPropagation(),!1})),t.prepend(expand)}))},reset:function(){var n=encodeURI(window.location.hash)||"#";try{var e=$(".wy-menu-vertical"),t=e.find('[href="'+n+'"]');if(0===t.length){var i=$('.document [id="'+n.substring(1)+'"]').closest("div.section");0===(t=e.find('[href="#'+i.attr("id")+'"]')).length&&(t=e.find('[href="#"]'))}if(t.length>0){$(".wy-menu-vertical .current").removeClass("current").attr("aria-expanded","false"),t.addClass("current").attr("aria-expanded","true"),t.closest("li.toctree-l1").parent().addClass("current").attr("aria-expanded","true");for(let n=1;n<=10;n++)t.closest("li.toctree-l"+n).addClass("current").attr("aria-expanded","true");t[0].scrollIntoView()}}catch(n){console.log("Error expanding nav for anchor",n)}},onScroll:function(){this.winScroll=!1;var n=this.win.scrollTop(),e=n+this.winHeight,t=this.navBar.scrollTop()+(n-this.winPosition);n<0||e>this.docHeight||(this.navBar.scrollTop(t),this.winPosition=n)},onResize:function(){this.winResize=!1,this.winHeight=this.win.height(),this.docHeight=$(document).height()},hashChange:function(){this.linkScroll=!0,this.win.one("hashchange",(function(){this.linkScroll=!1}))},toggleCurrent:function(n){var e=n.closest("li");e.siblings("li.current").removeClass("current").attr("aria-expanded","false"),e.siblings().find("li.current").removeClass("current").attr("aria-expanded","false");var t=e.find("> ul li");t.length&&(t.removeClass("current").attr("aria-expanded","false"),e.toggleClass("current").attr("aria-expanded",(function(n,e){return"true"==e?"false":"true"})))}},"undefined"!=typeof window&&(window.SphinxRtdTheme={Navigation:n.exports.ThemeNav,StickyNav:n.exports.ThemeNav}),function(){for(var n=0,e=["ms","moz","webkit","o"],t=0;t + + + + + + + FNA Docs + + + + + + + + + + + + +
+ + +
+ +
+
+
    +
  • +
  • +
  • +
+
+
+
+
+ + +

Search Results

+ + + +
+ Searching... +
+ + +
+
+ +
+
+ +
+ +
+ +
+ + + + GitHub + + + + + +
+ + + + + + + + + diff --git a/search/lunr.js b/search/lunr.js new file mode 100644 index 0000000..aca0a16 --- /dev/null +++ b/search/lunr.js @@ -0,0 +1,3475 @@ +/** + * lunr - http://lunrjs.com - A bit like Solr, but much smaller and not as bright - 2.3.9 + * Copyright (C) 2020 Oliver Nightingale + * @license MIT + */ + +;(function(){ + +/** + * A convenience function for configuring and constructing + * a new lunr Index. + * + * A lunr.Builder instance is created and the pipeline setup + * with a trimmer, stop word filter and stemmer. + * + * This builder object is yielded to the configuration function + * that is passed as a parameter, allowing the list of fields + * and other builder parameters to be customised. + * + * All documents _must_ be added within the passed config function. + * + * @example + * var idx = lunr(function () { + * this.field('title') + * this.field('body') + * this.ref('id') + * + * documents.forEach(function (doc) { + * this.add(doc) + * }, this) + * }) + * + * @see {@link lunr.Builder} + * @see {@link lunr.Pipeline} + * @see {@link lunr.trimmer} + * @see {@link lunr.stopWordFilter} + * @see {@link lunr.stemmer} + * @namespace {function} lunr + */ +var lunr = function (config) { + var builder = new lunr.Builder + + builder.pipeline.add( + lunr.trimmer, + lunr.stopWordFilter, + lunr.stemmer + ) + + builder.searchPipeline.add( + lunr.stemmer + ) + + config.call(builder, builder) + return builder.build() +} + +lunr.version = "2.3.9" +/*! + * lunr.utils + * Copyright (C) 2020 Oliver Nightingale + */ + +/** + * A namespace containing utils for the rest of the lunr library + * @namespace lunr.utils + */ +lunr.utils = {} + +/** + * Print a warning message to the console. + * + * @param {String} message The message to be printed. + * @memberOf lunr.utils + * @function + */ +lunr.utils.warn = (function (global) { + /* eslint-disable no-console */ + return function (message) { + if (global.console && console.warn) { + console.warn(message) + } + } + /* eslint-enable no-console */ +})(this) + +/** + * Convert an object to a string. + * + * In the case of `null` and `undefined` the function returns + * the empty string, in all other cases the result of calling + * `toString` on the passed object is returned. + * + * @param {Any} obj The object to convert to a string. + * @return {String} string representation of the passed object. + * @memberOf lunr.utils + */ +lunr.utils.asString = function (obj) { + if (obj === void 0 || obj === null) { + return "" + } else { + return obj.toString() + } +} + +/** + * Clones an object. + * + * Will create a copy of an existing object such that any mutations + * on the copy cannot affect the original. + * + * Only shallow objects are supported, passing a nested object to this + * function will cause a TypeError. + * + * Objects with primitives, and arrays of primitives are supported. + * + * @param {Object} obj The object to clone. + * @return {Object} a clone of the passed object. + * @throws {TypeError} when a nested object is passed. + * @memberOf Utils + */ +lunr.utils.clone = function (obj) { + if (obj === null || obj === undefined) { + return obj + } + + var clone = Object.create(null), + keys = Object.keys(obj) + + for (var i = 0; i < keys.length; i++) { + var key = keys[i], + val = obj[key] + + if (Array.isArray(val)) { + clone[key] = val.slice() + continue + } + + if (typeof val === 'string' || + typeof val === 'number' || + typeof val === 'boolean') { + clone[key] = val + continue + } + + throw new TypeError("clone is not deep and does not support nested objects") + } + + return clone +} +lunr.FieldRef = function (docRef, fieldName, stringValue) { + this.docRef = docRef + this.fieldName = fieldName + this._stringValue = stringValue +} + +lunr.FieldRef.joiner = "/" + +lunr.FieldRef.fromString = function (s) { + var n = s.indexOf(lunr.FieldRef.joiner) + + if (n === -1) { + throw "malformed field ref string" + } + + var fieldRef = s.slice(0, n), + docRef = s.slice(n + 1) + + return new lunr.FieldRef (docRef, fieldRef, s) +} + +lunr.FieldRef.prototype.toString = function () { + if (this._stringValue == undefined) { + this._stringValue = this.fieldName + lunr.FieldRef.joiner + this.docRef + } + + return this._stringValue +} +/*! + * lunr.Set + * Copyright (C) 2020 Oliver Nightingale + */ + +/** + * A lunr set. + * + * @constructor + */ +lunr.Set = function (elements) { + this.elements = Object.create(null) + + if (elements) { + this.length = elements.length + + for (var i = 0; i < this.length; i++) { + this.elements[elements[i]] = true + } + } else { + this.length = 0 + } +} + +/** + * A complete set that contains all elements. + * + * @static + * @readonly + * @type {lunr.Set} + */ +lunr.Set.complete = { + intersect: function (other) { + return other + }, + + union: function () { + return this + }, + + contains: function () { + return true + } +} + +/** + * An empty set that contains no elements. + * + * @static + * @readonly + * @type {lunr.Set} + */ +lunr.Set.empty = { + intersect: function () { + return this + }, + + union: function (other) { + return other + }, + + contains: function () { + return false + } +} + +/** + * Returns true if this set contains the specified object. + * + * @param {object} object - Object whose presence in this set is to be tested. + * @returns {boolean} - True if this set contains the specified object. + */ +lunr.Set.prototype.contains = function (object) { + return !!this.elements[object] +} + +/** + * Returns a new set containing only the elements that are present in both + * this set and the specified set. + * + * @param {lunr.Set} other - set to intersect with this set. + * @returns {lunr.Set} a new set that is the intersection of this and the specified set. + */ + +lunr.Set.prototype.intersect = function (other) { + var a, b, elements, intersection = [] + + if (other === lunr.Set.complete) { + return this + } + + if (other === lunr.Set.empty) { + return other + } + + if (this.length < other.length) { + a = this + b = other + } else { + a = other + b = this + } + + elements = Object.keys(a.elements) + + for (var i = 0; i < elements.length; i++) { + var element = elements[i] + if (element in b.elements) { + intersection.push(element) + } + } + + return new lunr.Set (intersection) +} + +/** + * Returns a new set combining the elements of this and the specified set. + * + * @param {lunr.Set} other - set to union with this set. + * @return {lunr.Set} a new set that is the union of this and the specified set. + */ + +lunr.Set.prototype.union = function (other) { + if (other === lunr.Set.complete) { + return lunr.Set.complete + } + + if (other === lunr.Set.empty) { + return this + } + + return new lunr.Set(Object.keys(this.elements).concat(Object.keys(other.elements))) +} +/** + * A function to calculate the inverse document frequency for + * a posting. This is shared between the builder and the index + * + * @private + * @param {object} posting - The posting for a given term + * @param {number} documentCount - The total number of documents. + */ +lunr.idf = function (posting, documentCount) { + var documentsWithTerm = 0 + + for (var fieldName in posting) { + if (fieldName == '_index') continue // Ignore the term index, its not a field + documentsWithTerm += Object.keys(posting[fieldName]).length + } + + var x = (documentCount - documentsWithTerm + 0.5) / (documentsWithTerm + 0.5) + + return Math.log(1 + Math.abs(x)) +} + +/** + * A token wraps a string representation of a token + * as it is passed through the text processing pipeline. + * + * @constructor + * @param {string} [str=''] - The string token being wrapped. + * @param {object} [metadata={}] - Metadata associated with this token. + */ +lunr.Token = function (str, metadata) { + this.str = str || "" + this.metadata = metadata || {} +} + +/** + * Returns the token string that is being wrapped by this object. + * + * @returns {string} + */ +lunr.Token.prototype.toString = function () { + return this.str +} + +/** + * A token update function is used when updating or optionally + * when cloning a token. + * + * @callback lunr.Token~updateFunction + * @param {string} str - The string representation of the token. + * @param {Object} metadata - All metadata associated with this token. + */ + +/** + * Applies the given function to the wrapped string token. + * + * @example + * token.update(function (str, metadata) { + * return str.toUpperCase() + * }) + * + * @param {lunr.Token~updateFunction} fn - A function to apply to the token string. + * @returns {lunr.Token} + */ +lunr.Token.prototype.update = function (fn) { + this.str = fn(this.str, this.metadata) + return this +} + +/** + * Creates a clone of this token. Optionally a function can be + * applied to the cloned token. + * + * @param {lunr.Token~updateFunction} [fn] - An optional function to apply to the cloned token. + * @returns {lunr.Token} + */ +lunr.Token.prototype.clone = function (fn) { + fn = fn || function (s) { return s } + return new lunr.Token (fn(this.str, this.metadata), this.metadata) +} +/*! + * lunr.tokenizer + * Copyright (C) 2020 Oliver Nightingale + */ + +/** + * A function for splitting a string into tokens ready to be inserted into + * the search index. Uses `lunr.tokenizer.separator` to split strings, change + * the value of this property to change how strings are split into tokens. + * + * This tokenizer will convert its parameter to a string by calling `toString` and + * then will split this string on the character in `lunr.tokenizer.separator`. + * Arrays will have their elements converted to strings and wrapped in a lunr.Token. + * + * Optional metadata can be passed to the tokenizer, this metadata will be cloned and + * added as metadata to every token that is created from the object to be tokenized. + * + * @static + * @param {?(string|object|object[])} obj - The object to convert into tokens + * @param {?object} metadata - Optional metadata to associate with every token + * @returns {lunr.Token[]} + * @see {@link lunr.Pipeline} + */ +lunr.tokenizer = function (obj, metadata) { + if (obj == null || obj == undefined) { + return [] + } + + if (Array.isArray(obj)) { + return obj.map(function (t) { + return new lunr.Token( + lunr.utils.asString(t).toLowerCase(), + lunr.utils.clone(metadata) + ) + }) + } + + var str = obj.toString().toLowerCase(), + len = str.length, + tokens = [] + + for (var sliceEnd = 0, sliceStart = 0; sliceEnd <= len; sliceEnd++) { + var char = str.charAt(sliceEnd), + sliceLength = sliceEnd - sliceStart + + if ((char.match(lunr.tokenizer.separator) || sliceEnd == len)) { + + if (sliceLength > 0) { + var tokenMetadata = lunr.utils.clone(metadata) || {} + tokenMetadata["position"] = [sliceStart, sliceLength] + tokenMetadata["index"] = tokens.length + + tokens.push( + new lunr.Token ( + str.slice(sliceStart, sliceEnd), + tokenMetadata + ) + ) + } + + sliceStart = sliceEnd + 1 + } + + } + + return tokens +} + +/** + * The separator used to split a string into tokens. Override this property to change the behaviour of + * `lunr.tokenizer` behaviour when tokenizing strings. By default this splits on whitespace and hyphens. + * + * @static + * @see lunr.tokenizer + */ +lunr.tokenizer.separator = /[\s\-]+/ +/*! + * lunr.Pipeline + * Copyright (C) 2020 Oliver Nightingale + */ + +/** + * lunr.Pipelines maintain an ordered list of functions to be applied to all + * tokens in documents entering the search index and queries being ran against + * the index. + * + * An instance of lunr.Index created with the lunr shortcut will contain a + * pipeline with a stop word filter and an English language stemmer. Extra + * functions can be added before or after either of these functions or these + * default functions can be removed. + * + * When run the pipeline will call each function in turn, passing a token, the + * index of that token in the original list of all tokens and finally a list of + * all the original tokens. + * + * The output of functions in the pipeline will be passed to the next function + * in the pipeline. To exclude a token from entering the index the function + * should return undefined, the rest of the pipeline will not be called with + * this token. + * + * For serialisation of pipelines to work, all functions used in an instance of + * a pipeline should be registered with lunr.Pipeline. Registered functions can + * then be loaded. If trying to load a serialised pipeline that uses functions + * that are not registered an error will be thrown. + * + * If not planning on serialising the pipeline then registering pipeline functions + * is not necessary. + * + * @constructor + */ +lunr.Pipeline = function () { + this._stack = [] +} + +lunr.Pipeline.registeredFunctions = Object.create(null) + +/** + * A pipeline function maps lunr.Token to lunr.Token. A lunr.Token contains the token + * string as well as all known metadata. A pipeline function can mutate the token string + * or mutate (or add) metadata for a given token. + * + * A pipeline function can indicate that the passed token should be discarded by returning + * null, undefined or an empty string. This token will not be passed to any downstream pipeline + * functions and will not be added to the index. + * + * Multiple tokens can be returned by returning an array of tokens. Each token will be passed + * to any downstream pipeline functions and all will returned tokens will be added to the index. + * + * Any number of pipeline functions may be chained together using a lunr.Pipeline. + * + * @interface lunr.PipelineFunction + * @param {lunr.Token} token - A token from the document being processed. + * @param {number} i - The index of this token in the complete list of tokens for this document/field. + * @param {lunr.Token[]} tokens - All tokens for this document/field. + * @returns {(?lunr.Token|lunr.Token[])} + */ + +/** + * Register a function with the pipeline. + * + * Functions that are used in the pipeline should be registered if the pipeline + * needs to be serialised, or a serialised pipeline needs to be loaded. + * + * Registering a function does not add it to a pipeline, functions must still be + * added to instances of the pipeline for them to be used when running a pipeline. + * + * @param {lunr.PipelineFunction} fn - The function to check for. + * @param {String} label - The label to register this function with + */ +lunr.Pipeline.registerFunction = function (fn, label) { + if (label in this.registeredFunctions) { + lunr.utils.warn('Overwriting existing registered function: ' + label) + } + + fn.label = label + lunr.Pipeline.registeredFunctions[fn.label] = fn +} + +/** + * Warns if the function is not registered as a Pipeline function. + * + * @param {lunr.PipelineFunction} fn - The function to check for. + * @private + */ +lunr.Pipeline.warnIfFunctionNotRegistered = function (fn) { + var isRegistered = fn.label && (fn.label in this.registeredFunctions) + + if (!isRegistered) { + lunr.utils.warn('Function is not registered with pipeline. This may cause problems when serialising the index.\n', fn) + } +} + +/** + * Loads a previously serialised pipeline. + * + * All functions to be loaded must already be registered with lunr.Pipeline. + * If any function from the serialised data has not been registered then an + * error will be thrown. + * + * @param {Object} serialised - The serialised pipeline to load. + * @returns {lunr.Pipeline} + */ +lunr.Pipeline.load = function (serialised) { + var pipeline = new lunr.Pipeline + + serialised.forEach(function (fnName) { + var fn = lunr.Pipeline.registeredFunctions[fnName] + + if (fn) { + pipeline.add(fn) + } else { + throw new Error('Cannot load unregistered function: ' + fnName) + } + }) + + return pipeline +} + +/** + * Adds new functions to the end of the pipeline. + * + * Logs a warning if the function has not been registered. + * + * @param {lunr.PipelineFunction[]} functions - Any number of functions to add to the pipeline. + */ +lunr.Pipeline.prototype.add = function () { + var fns = Array.prototype.slice.call(arguments) + + fns.forEach(function (fn) { + lunr.Pipeline.warnIfFunctionNotRegistered(fn) + this._stack.push(fn) + }, this) +} + +/** + * Adds a single function after a function that already exists in the + * pipeline. + * + * Logs a warning if the function has not been registered. + * + * @param {lunr.PipelineFunction} existingFn - A function that already exists in the pipeline. + * @param {lunr.PipelineFunction} newFn - The new function to add to the pipeline. + */ +lunr.Pipeline.prototype.after = function (existingFn, newFn) { + lunr.Pipeline.warnIfFunctionNotRegistered(newFn) + + var pos = this._stack.indexOf(existingFn) + if (pos == -1) { + throw new Error('Cannot find existingFn') + } + + pos = pos + 1 + this._stack.splice(pos, 0, newFn) +} + +/** + * Adds a single function before a function that already exists in the + * pipeline. + * + * Logs a warning if the function has not been registered. + * + * @param {lunr.PipelineFunction} existingFn - A function that already exists in the pipeline. + * @param {lunr.PipelineFunction} newFn - The new function to add to the pipeline. + */ +lunr.Pipeline.prototype.before = function (existingFn, newFn) { + lunr.Pipeline.warnIfFunctionNotRegistered(newFn) + + var pos = this._stack.indexOf(existingFn) + if (pos == -1) { + throw new Error('Cannot find existingFn') + } + + this._stack.splice(pos, 0, newFn) +} + +/** + * Removes a function from the pipeline. + * + * @param {lunr.PipelineFunction} fn The function to remove from the pipeline. + */ +lunr.Pipeline.prototype.remove = function (fn) { + var pos = this._stack.indexOf(fn) + if (pos == -1) { + return + } + + this._stack.splice(pos, 1) +} + +/** + * Runs the current list of functions that make up the pipeline against the + * passed tokens. + * + * @param {Array} tokens The tokens to run through the pipeline. + * @returns {Array} + */ +lunr.Pipeline.prototype.run = function (tokens) { + var stackLength = this._stack.length + + for (var i = 0; i < stackLength; i++) { + var fn = this._stack[i] + var memo = [] + + for (var j = 0; j < tokens.length; j++) { + var result = fn(tokens[j], j, tokens) + + if (result === null || result === void 0 || result === '') continue + + if (Array.isArray(result)) { + for (var k = 0; k < result.length; k++) { + memo.push(result[k]) + } + } else { + memo.push(result) + } + } + + tokens = memo + } + + return tokens +} + +/** + * Convenience method for passing a string through a pipeline and getting + * strings out. This method takes care of wrapping the passed string in a + * token and mapping the resulting tokens back to strings. + * + * @param {string} str - The string to pass through the pipeline. + * @param {?object} metadata - Optional metadata to associate with the token + * passed to the pipeline. + * @returns {string[]} + */ +lunr.Pipeline.prototype.runString = function (str, metadata) { + var token = new lunr.Token (str, metadata) + + return this.run([token]).map(function (t) { + return t.toString() + }) +} + +/** + * Resets the pipeline by removing any existing processors. + * + */ +lunr.Pipeline.prototype.reset = function () { + this._stack = [] +} + +/** + * Returns a representation of the pipeline ready for serialisation. + * + * Logs a warning if the function has not been registered. + * + * @returns {Array} + */ +lunr.Pipeline.prototype.toJSON = function () { + return this._stack.map(function (fn) { + lunr.Pipeline.warnIfFunctionNotRegistered(fn) + + return fn.label + }) +} +/*! + * lunr.Vector + * Copyright (C) 2020 Oliver Nightingale + */ + +/** + * A vector is used to construct the vector space of documents and queries. These + * vectors support operations to determine the similarity between two documents or + * a document and a query. + * + * Normally no parameters are required for initializing a vector, but in the case of + * loading a previously dumped vector the raw elements can be provided to the constructor. + * + * For performance reasons vectors are implemented with a flat array, where an elements + * index is immediately followed by its value. E.g. [index, value, index, value]. This + * allows the underlying array to be as sparse as possible and still offer decent + * performance when being used for vector calculations. + * + * @constructor + * @param {Number[]} [elements] - The flat list of element index and element value pairs. + */ +lunr.Vector = function (elements) { + this._magnitude = 0 + this.elements = elements || [] +} + + +/** + * Calculates the position within the vector to insert a given index. + * + * This is used internally by insert and upsert. If there are duplicate indexes then + * the position is returned as if the value for that index were to be updated, but it + * is the callers responsibility to check whether there is a duplicate at that index + * + * @param {Number} insertIdx - The index at which the element should be inserted. + * @returns {Number} + */ +lunr.Vector.prototype.positionForIndex = function (index) { + // For an empty vector the tuple can be inserted at the beginning + if (this.elements.length == 0) { + return 0 + } + + var start = 0, + end = this.elements.length / 2, + sliceLength = end - start, + pivotPoint = Math.floor(sliceLength / 2), + pivotIndex = this.elements[pivotPoint * 2] + + while (sliceLength > 1) { + if (pivotIndex < index) { + start = pivotPoint + } + + if (pivotIndex > index) { + end = pivotPoint + } + + if (pivotIndex == index) { + break + } + + sliceLength = end - start + pivotPoint = start + Math.floor(sliceLength / 2) + pivotIndex = this.elements[pivotPoint * 2] + } + + if (pivotIndex == index) { + return pivotPoint * 2 + } + + if (pivotIndex > index) { + return pivotPoint * 2 + } + + if (pivotIndex < index) { + return (pivotPoint + 1) * 2 + } +} + +/** + * Inserts an element at an index within the vector. + * + * Does not allow duplicates, will throw an error if there is already an entry + * for this index. + * + * @param {Number} insertIdx - The index at which the element should be inserted. + * @param {Number} val - The value to be inserted into the vector. + */ +lunr.Vector.prototype.insert = function (insertIdx, val) { + this.upsert(insertIdx, val, function () { + throw "duplicate index" + }) +} + +/** + * Inserts or updates an existing index within the vector. + * + * @param {Number} insertIdx - The index at which the element should be inserted. + * @param {Number} val - The value to be inserted into the vector. + * @param {function} fn - A function that is called for updates, the existing value and the + * requested value are passed as arguments + */ +lunr.Vector.prototype.upsert = function (insertIdx, val, fn) { + this._magnitude = 0 + var position = this.positionForIndex(insertIdx) + + if (this.elements[position] == insertIdx) { + this.elements[position + 1] = fn(this.elements[position + 1], val) + } else { + this.elements.splice(position, 0, insertIdx, val) + } +} + +/** + * Calculates the magnitude of this vector. + * + * @returns {Number} + */ +lunr.Vector.prototype.magnitude = function () { + if (this._magnitude) return this._magnitude + + var sumOfSquares = 0, + elementsLength = this.elements.length + + for (var i = 1; i < elementsLength; i += 2) { + var val = this.elements[i] + sumOfSquares += val * val + } + + return this._magnitude = Math.sqrt(sumOfSquares) +} + +/** + * Calculates the dot product of this vector and another vector. + * + * @param {lunr.Vector} otherVector - The vector to compute the dot product with. + * @returns {Number} + */ +lunr.Vector.prototype.dot = function (otherVector) { + var dotProduct = 0, + a = this.elements, b = otherVector.elements, + aLen = a.length, bLen = b.length, + aVal = 0, bVal = 0, + i = 0, j = 0 + + while (i < aLen && j < bLen) { + aVal = a[i], bVal = b[j] + if (aVal < bVal) { + i += 2 + } else if (aVal > bVal) { + j += 2 + } else if (aVal == bVal) { + dotProduct += a[i + 1] * b[j + 1] + i += 2 + j += 2 + } + } + + return dotProduct +} + +/** + * Calculates the similarity between this vector and another vector. + * + * @param {lunr.Vector} otherVector - The other vector to calculate the + * similarity with. + * @returns {Number} + */ +lunr.Vector.prototype.similarity = function (otherVector) { + return this.dot(otherVector) / this.magnitude() || 0 +} + +/** + * Converts the vector to an array of the elements within the vector. + * + * @returns {Number[]} + */ +lunr.Vector.prototype.toArray = function () { + var output = new Array (this.elements.length / 2) + + for (var i = 1, j = 0; i < this.elements.length; i += 2, j++) { + output[j] = this.elements[i] + } + + return output +} + +/** + * A JSON serializable representation of the vector. + * + * @returns {Number[]} + */ +lunr.Vector.prototype.toJSON = function () { + return this.elements +} +/* eslint-disable */ +/*! + * lunr.stemmer + * Copyright (C) 2020 Oliver Nightingale + * Includes code from - http://tartarus.org/~martin/PorterStemmer/js.txt + */ + +/** + * lunr.stemmer is an english language stemmer, this is a JavaScript + * implementation of the PorterStemmer taken from http://tartarus.org/~martin + * + * @static + * @implements {lunr.PipelineFunction} + * @param {lunr.Token} token - The string to stem + * @returns {lunr.Token} + * @see {@link lunr.Pipeline} + * @function + */ +lunr.stemmer = (function(){ + var step2list = { + "ational" : "ate", + "tional" : "tion", + "enci" : "ence", + "anci" : "ance", + "izer" : "ize", + "bli" : "ble", + "alli" : "al", + "entli" : "ent", + "eli" : "e", + "ousli" : "ous", + "ization" : "ize", + "ation" : "ate", + "ator" : "ate", + "alism" : "al", + "iveness" : "ive", + "fulness" : "ful", + "ousness" : "ous", + "aliti" : "al", + "iviti" : "ive", + "biliti" : "ble", + "logi" : "log" + }, + + step3list = { + "icate" : "ic", + "ative" : "", + "alize" : "al", + "iciti" : "ic", + "ical" : "ic", + "ful" : "", + "ness" : "" + }, + + c = "[^aeiou]", // consonant + v = "[aeiouy]", // vowel + C = c + "[^aeiouy]*", // consonant sequence + V = v + "[aeiou]*", // vowel sequence + + mgr0 = "^(" + C + ")?" + V + C, // [C]VC... is m>0 + meq1 = "^(" + C + ")?" + V + C + "(" + V + ")?$", // [C]VC[V] is m=1 + mgr1 = "^(" + C + ")?" + V + C + V + C, // [C]VCVC... is m>1 + s_v = "^(" + C + ")?" + v; // vowel in stem + + var re_mgr0 = new RegExp(mgr0); + var re_mgr1 = new RegExp(mgr1); + var re_meq1 = new RegExp(meq1); + var re_s_v = new RegExp(s_v); + + var re_1a = /^(.+?)(ss|i)es$/; + var re2_1a = /^(.+?)([^s])s$/; + var re_1b = /^(.+?)eed$/; + var re2_1b = /^(.+?)(ed|ing)$/; + var re_1b_2 = /.$/; + var re2_1b_2 = /(at|bl|iz)$/; + var re3_1b_2 = new RegExp("([^aeiouylsz])\\1$"); + var re4_1b_2 = new RegExp("^" + C + v + "[^aeiouwxy]$"); + + var re_1c = /^(.+?[^aeiou])y$/; + var re_2 = /^(.+?)(ational|tional|enci|anci|izer|bli|alli|entli|eli|ousli|ization|ation|ator|alism|iveness|fulness|ousness|aliti|iviti|biliti|logi)$/; + + var re_3 = /^(.+?)(icate|ative|alize|iciti|ical|ful|ness)$/; + + var re_4 = /^(.+?)(al|ance|ence|er|ic|able|ible|ant|ement|ment|ent|ou|ism|ate|iti|ous|ive|ize)$/; + var re2_4 = /^(.+?)(s|t)(ion)$/; + + var re_5 = /^(.+?)e$/; + var re_5_1 = /ll$/; + var re3_5 = new RegExp("^" + C + v + "[^aeiouwxy]$"); + + var porterStemmer = function porterStemmer(w) { + var stem, + suffix, + firstch, + re, + re2, + re3, + re4; + + if (w.length < 3) { return w; } + + firstch = w.substr(0,1); + if (firstch == "y") { + w = firstch.toUpperCase() + w.substr(1); + } + + // Step 1a + re = re_1a + re2 = re2_1a; + + if (re.test(w)) { w = w.replace(re,"$1$2"); } + else if (re2.test(w)) { w = w.replace(re2,"$1$2"); } + + // Step 1b + re = re_1b; + re2 = re2_1b; + if (re.test(w)) { + var fp = re.exec(w); + re = re_mgr0; + if (re.test(fp[1])) { + re = re_1b_2; + w = w.replace(re,""); + } + } else if (re2.test(w)) { + var fp = re2.exec(w); + stem = fp[1]; + re2 = re_s_v; + if (re2.test(stem)) { + w = stem; + re2 = re2_1b_2; + re3 = re3_1b_2; + re4 = re4_1b_2; + if (re2.test(w)) { w = w + "e"; } + else if (re3.test(w)) { re = re_1b_2; w = w.replace(re,""); } + else if (re4.test(w)) { w = w + "e"; } + } + } + + // Step 1c - replace suffix y or Y by i if preceded by a non-vowel which is not the first letter of the word (so cry -> cri, by -> by, say -> say) + re = re_1c; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + w = stem + "i"; + } + + // Step 2 + re = re_2; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + suffix = fp[2]; + re = re_mgr0; + if (re.test(stem)) { + w = stem + step2list[suffix]; + } + } + + // Step 3 + re = re_3; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + suffix = fp[2]; + re = re_mgr0; + if (re.test(stem)) { + w = stem + step3list[suffix]; + } + } + + // Step 4 + re = re_4; + re2 = re2_4; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + re = re_mgr1; + if (re.test(stem)) { + w = stem; + } + } else if (re2.test(w)) { + var fp = re2.exec(w); + stem = fp[1] + fp[2]; + re2 = re_mgr1; + if (re2.test(stem)) { + w = stem; + } + } + + // Step 5 + re = re_5; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + re = re_mgr1; + re2 = re_meq1; + re3 = re3_5; + if (re.test(stem) || (re2.test(stem) && !(re3.test(stem)))) { + w = stem; + } + } + + re = re_5_1; + re2 = re_mgr1; + if (re.test(w) && re2.test(w)) { + re = re_1b_2; + w = w.replace(re,""); + } + + // and turn initial Y back to y + + if (firstch == "y") { + w = firstch.toLowerCase() + w.substr(1); + } + + return w; + }; + + return function (token) { + return token.update(porterStemmer); + } +})(); + +lunr.Pipeline.registerFunction(lunr.stemmer, 'stemmer') +/*! + * lunr.stopWordFilter + * Copyright (C) 2020 Oliver Nightingale + */ + +/** + * lunr.generateStopWordFilter builds a stopWordFilter function from the provided + * list of stop words. + * + * The built in lunr.stopWordFilter is built using this generator and can be used + * to generate custom stopWordFilters for applications or non English languages. + * + * @function + * @param {Array} token The token to pass through the filter + * @returns {lunr.PipelineFunction} + * @see lunr.Pipeline + * @see lunr.stopWordFilter + */ +lunr.generateStopWordFilter = function (stopWords) { + var words = stopWords.reduce(function (memo, stopWord) { + memo[stopWord] = stopWord + return memo + }, {}) + + return function (token) { + if (token && words[token.toString()] !== token.toString()) return token + } +} + +/** + * lunr.stopWordFilter is an English language stop word list filter, any words + * contained in the list will not be passed through the filter. + * + * This is intended to be used in the Pipeline. If the token does not pass the + * filter then undefined will be returned. + * + * @function + * @implements {lunr.PipelineFunction} + * @params {lunr.Token} token - A token to check for being a stop word. + * @returns {lunr.Token} + * @see {@link lunr.Pipeline} + */ +lunr.stopWordFilter = lunr.generateStopWordFilter([ + 'a', + 'able', + 'about', + 'across', + 'after', + 'all', + 'almost', + 'also', + 'am', + 'among', + 'an', + 'and', + 'any', + 'are', + 'as', + 'at', + 'be', + 'because', + 'been', + 'but', + 'by', + 'can', + 'cannot', + 'could', + 'dear', + 'did', + 'do', + 'does', + 'either', + 'else', + 'ever', + 'every', + 'for', + 'from', + 'get', + 'got', + 'had', + 'has', + 'have', + 'he', + 'her', + 'hers', + 'him', + 'his', + 'how', + 'however', + 'i', + 'if', + 'in', + 'into', + 'is', + 'it', + 'its', + 'just', + 'least', + 'let', + 'like', + 'likely', + 'may', + 'me', + 'might', + 'most', + 'must', + 'my', + 'neither', + 'no', + 'nor', + 'not', + 'of', + 'off', + 'often', + 'on', + 'only', + 'or', + 'other', + 'our', + 'own', + 'rather', + 'said', + 'say', + 'says', + 'she', + 'should', + 'since', + 'so', + 'some', + 'than', + 'that', + 'the', + 'their', + 'them', + 'then', + 'there', + 'these', + 'they', + 'this', + 'tis', + 'to', + 'too', + 'twas', + 'us', + 'wants', + 'was', + 'we', + 'were', + 'what', + 'when', + 'where', + 'which', + 'while', + 'who', + 'whom', + 'why', + 'will', + 'with', + 'would', + 'yet', + 'you', + 'your' +]) + +lunr.Pipeline.registerFunction(lunr.stopWordFilter, 'stopWordFilter') +/*! + * lunr.trimmer + * Copyright (C) 2020 Oliver Nightingale + */ + +/** + * lunr.trimmer is a pipeline function for trimming non word + * characters from the beginning and end of tokens before they + * enter the index. + * + * This implementation may not work correctly for non latin + * characters and should either be removed or adapted for use + * with languages with non-latin characters. + * + * @static + * @implements {lunr.PipelineFunction} + * @param {lunr.Token} token The token to pass through the filter + * @returns {lunr.Token} + * @see lunr.Pipeline + */ +lunr.trimmer = function (token) { + return token.update(function (s) { + return s.replace(/^\W+/, '').replace(/\W+$/, '') + }) +} + +lunr.Pipeline.registerFunction(lunr.trimmer, 'trimmer') +/*! + * lunr.TokenSet + * Copyright (C) 2020 Oliver Nightingale + */ + +/** + * A token set is used to store the unique list of all tokens + * within an index. Token sets are also used to represent an + * incoming query to the index, this query token set and index + * token set are then intersected to find which tokens to look + * up in the inverted index. + * + * A token set can hold multiple tokens, as in the case of the + * index token set, or it can hold a single token as in the + * case of a simple query token set. + * + * Additionally token sets are used to perform wildcard matching. + * Leading, contained and trailing wildcards are supported, and + * from this edit distance matching can also be provided. + * + * Token sets are implemented as a minimal finite state automata, + * where both common prefixes and suffixes are shared between tokens. + * This helps to reduce the space used for storing the token set. + * + * @constructor + */ +lunr.TokenSet = function () { + this.final = false + this.edges = {} + this.id = lunr.TokenSet._nextId + lunr.TokenSet._nextId += 1 +} + +/** + * Keeps track of the next, auto increment, identifier to assign + * to a new tokenSet. + * + * TokenSets require a unique identifier to be correctly minimised. + * + * @private + */ +lunr.TokenSet._nextId = 1 + +/** + * Creates a TokenSet instance from the given sorted array of words. + * + * @param {String[]} arr - A sorted array of strings to create the set from. + * @returns {lunr.TokenSet} + * @throws Will throw an error if the input array is not sorted. + */ +lunr.TokenSet.fromArray = function (arr) { + var builder = new lunr.TokenSet.Builder + + for (var i = 0, len = arr.length; i < len; i++) { + builder.insert(arr[i]) + } + + builder.finish() + return builder.root +} + +/** + * Creates a token set from a query clause. + * + * @private + * @param {Object} clause - A single clause from lunr.Query. + * @param {string} clause.term - The query clause term. + * @param {number} [clause.editDistance] - The optional edit distance for the term. + * @returns {lunr.TokenSet} + */ +lunr.TokenSet.fromClause = function (clause) { + if ('editDistance' in clause) { + return lunr.TokenSet.fromFuzzyString(clause.term, clause.editDistance) + } else { + return lunr.TokenSet.fromString(clause.term) + } +} + +/** + * Creates a token set representing a single string with a specified + * edit distance. + * + * Insertions, deletions, substitutions and transpositions are each + * treated as an edit distance of 1. + * + * Increasing the allowed edit distance will have a dramatic impact + * on the performance of both creating and intersecting these TokenSets. + * It is advised to keep the edit distance less than 3. + * + * @param {string} str - The string to create the token set from. + * @param {number} editDistance - The allowed edit distance to match. + * @returns {lunr.Vector} + */ +lunr.TokenSet.fromFuzzyString = function (str, editDistance) { + var root = new lunr.TokenSet + + var stack = [{ + node: root, + editsRemaining: editDistance, + str: str + }] + + while (stack.length) { + var frame = stack.pop() + + // no edit + if (frame.str.length > 0) { + var char = frame.str.charAt(0), + noEditNode + + if (char in frame.node.edges) { + noEditNode = frame.node.edges[char] + } else { + noEditNode = new lunr.TokenSet + frame.node.edges[char] = noEditNode + } + + if (frame.str.length == 1) { + noEditNode.final = true + } + + stack.push({ + node: noEditNode, + editsRemaining: frame.editsRemaining, + str: frame.str.slice(1) + }) + } + + if (frame.editsRemaining == 0) { + continue + } + + // insertion + if ("*" in frame.node.edges) { + var insertionNode = frame.node.edges["*"] + } else { + var insertionNode = new lunr.TokenSet + frame.node.edges["*"] = insertionNode + } + + if (frame.str.length == 0) { + insertionNode.final = true + } + + stack.push({ + node: insertionNode, + editsRemaining: frame.editsRemaining - 1, + str: frame.str + }) + + // deletion + // can only do a deletion if we have enough edits remaining + // and if there are characters left to delete in the string + if (frame.str.length > 1) { + stack.push({ + node: frame.node, + editsRemaining: frame.editsRemaining - 1, + str: frame.str.slice(1) + }) + } + + // deletion + // just removing the last character from the str + if (frame.str.length == 1) { + frame.node.final = true + } + + // substitution + // can only do a substitution if we have enough edits remaining + // and if there are characters left to substitute + if (frame.str.length >= 1) { + if ("*" in frame.node.edges) { + var substitutionNode = frame.node.edges["*"] + } else { + var substitutionNode = new lunr.TokenSet + frame.node.edges["*"] = substitutionNode + } + + if (frame.str.length == 1) { + substitutionNode.final = true + } + + stack.push({ + node: substitutionNode, + editsRemaining: frame.editsRemaining - 1, + str: frame.str.slice(1) + }) + } + + // transposition + // can only do a transposition if there are edits remaining + // and there are enough characters to transpose + if (frame.str.length > 1) { + var charA = frame.str.charAt(0), + charB = frame.str.charAt(1), + transposeNode + + if (charB in frame.node.edges) { + transposeNode = frame.node.edges[charB] + } else { + transposeNode = new lunr.TokenSet + frame.node.edges[charB] = transposeNode + } + + if (frame.str.length == 1) { + transposeNode.final = true + } + + stack.push({ + node: transposeNode, + editsRemaining: frame.editsRemaining - 1, + str: charA + frame.str.slice(2) + }) + } + } + + return root +} + +/** + * Creates a TokenSet from a string. + * + * The string may contain one or more wildcard characters (*) + * that will allow wildcard matching when intersecting with + * another TokenSet. + * + * @param {string} str - The string to create a TokenSet from. + * @returns {lunr.TokenSet} + */ +lunr.TokenSet.fromString = function (str) { + var node = new lunr.TokenSet, + root = node + + /* + * Iterates through all characters within the passed string + * appending a node for each character. + * + * When a wildcard character is found then a self + * referencing edge is introduced to continually match + * any number of any characters. + */ + for (var i = 0, len = str.length; i < len; i++) { + var char = str[i], + final = (i == len - 1) + + if (char == "*") { + node.edges[char] = node + node.final = final + + } else { + var next = new lunr.TokenSet + next.final = final + + node.edges[char] = next + node = next + } + } + + return root +} + +/** + * Converts this TokenSet into an array of strings + * contained within the TokenSet. + * + * This is not intended to be used on a TokenSet that + * contains wildcards, in these cases the results are + * undefined and are likely to cause an infinite loop. + * + * @returns {string[]} + */ +lunr.TokenSet.prototype.toArray = function () { + var words = [] + + var stack = [{ + prefix: "", + node: this + }] + + while (stack.length) { + var frame = stack.pop(), + edges = Object.keys(frame.node.edges), + len = edges.length + + if (frame.node.final) { + /* In Safari, at this point the prefix is sometimes corrupted, see: + * https://github.com/olivernn/lunr.js/issues/279 Calling any + * String.prototype method forces Safari to "cast" this string to what + * it's supposed to be, fixing the bug. */ + frame.prefix.charAt(0) + words.push(frame.prefix) + } + + for (var i = 0; i < len; i++) { + var edge = edges[i] + + stack.push({ + prefix: frame.prefix.concat(edge), + node: frame.node.edges[edge] + }) + } + } + + return words +} + +/** + * Generates a string representation of a TokenSet. + * + * This is intended to allow TokenSets to be used as keys + * in objects, largely to aid the construction and minimisation + * of a TokenSet. As such it is not designed to be a human + * friendly representation of the TokenSet. + * + * @returns {string} + */ +lunr.TokenSet.prototype.toString = function () { + // NOTE: Using Object.keys here as this.edges is very likely + // to enter 'hash-mode' with many keys being added + // + // avoiding a for-in loop here as it leads to the function + // being de-optimised (at least in V8). From some simple + // benchmarks the performance is comparable, but allowing + // V8 to optimize may mean easy performance wins in the future. + + if (this._str) { + return this._str + } + + var str = this.final ? '1' : '0', + labels = Object.keys(this.edges).sort(), + len = labels.length + + for (var i = 0; i < len; i++) { + var label = labels[i], + node = this.edges[label] + + str = str + label + node.id + } + + return str +} + +/** + * Returns a new TokenSet that is the intersection of + * this TokenSet and the passed TokenSet. + * + * This intersection will take into account any wildcards + * contained within the TokenSet. + * + * @param {lunr.TokenSet} b - An other TokenSet to intersect with. + * @returns {lunr.TokenSet} + */ +lunr.TokenSet.prototype.intersect = function (b) { + var output = new lunr.TokenSet, + frame = undefined + + var stack = [{ + qNode: b, + output: output, + node: this + }] + + while (stack.length) { + frame = stack.pop() + + // NOTE: As with the #toString method, we are using + // Object.keys and a for loop instead of a for-in loop + // as both of these objects enter 'hash' mode, causing + // the function to be de-optimised in V8 + var qEdges = Object.keys(frame.qNode.edges), + qLen = qEdges.length, + nEdges = Object.keys(frame.node.edges), + nLen = nEdges.length + + for (var q = 0; q < qLen; q++) { + var qEdge = qEdges[q] + + for (var n = 0; n < nLen; n++) { + var nEdge = nEdges[n] + + if (nEdge == qEdge || qEdge == '*') { + var node = frame.node.edges[nEdge], + qNode = frame.qNode.edges[qEdge], + final = node.final && qNode.final, + next = undefined + + if (nEdge in frame.output.edges) { + // an edge already exists for this character + // no need to create a new node, just set the finality + // bit unless this node is already final + next = frame.output.edges[nEdge] + next.final = next.final || final + + } else { + // no edge exists yet, must create one + // set the finality bit and insert it + // into the output + next = new lunr.TokenSet + next.final = final + frame.output.edges[nEdge] = next + } + + stack.push({ + qNode: qNode, + output: next, + node: node + }) + } + } + } + } + + return output +} +lunr.TokenSet.Builder = function () { + this.previousWord = "" + this.root = new lunr.TokenSet + this.uncheckedNodes = [] + this.minimizedNodes = {} +} + +lunr.TokenSet.Builder.prototype.insert = function (word) { + var node, + commonPrefix = 0 + + if (word < this.previousWord) { + throw new Error ("Out of order word insertion") + } + + for (var i = 0; i < word.length && i < this.previousWord.length; i++) { + if (word[i] != this.previousWord[i]) break + commonPrefix++ + } + + this.minimize(commonPrefix) + + if (this.uncheckedNodes.length == 0) { + node = this.root + } else { + node = this.uncheckedNodes[this.uncheckedNodes.length - 1].child + } + + for (var i = commonPrefix; i < word.length; i++) { + var nextNode = new lunr.TokenSet, + char = word[i] + + node.edges[char] = nextNode + + this.uncheckedNodes.push({ + parent: node, + char: char, + child: nextNode + }) + + node = nextNode + } + + node.final = true + this.previousWord = word +} + +lunr.TokenSet.Builder.prototype.finish = function () { + this.minimize(0) +} + +lunr.TokenSet.Builder.prototype.minimize = function (downTo) { + for (var i = this.uncheckedNodes.length - 1; i >= downTo; i--) { + var node = this.uncheckedNodes[i], + childKey = node.child.toString() + + if (childKey in this.minimizedNodes) { + node.parent.edges[node.char] = this.minimizedNodes[childKey] + } else { + // Cache the key for this node since + // we know it can't change anymore + node.child._str = childKey + + this.minimizedNodes[childKey] = node.child + } + + this.uncheckedNodes.pop() + } +} +/*! + * lunr.Index + * Copyright (C) 2020 Oliver Nightingale + */ + +/** + * An index contains the built index of all documents and provides a query interface + * to the index. + * + * Usually instances of lunr.Index will not be created using this constructor, instead + * lunr.Builder should be used to construct new indexes, or lunr.Index.load should be + * used to load previously built and serialized indexes. + * + * @constructor + * @param {Object} attrs - The attributes of the built search index. + * @param {Object} attrs.invertedIndex - An index of term/field to document reference. + * @param {Object} attrs.fieldVectors - Field vectors + * @param {lunr.TokenSet} attrs.tokenSet - An set of all corpus tokens. + * @param {string[]} attrs.fields - The names of indexed document fields. + * @param {lunr.Pipeline} attrs.pipeline - The pipeline to use for search terms. + */ +lunr.Index = function (attrs) { + this.invertedIndex = attrs.invertedIndex + this.fieldVectors = attrs.fieldVectors + this.tokenSet = attrs.tokenSet + this.fields = attrs.fields + this.pipeline = attrs.pipeline +} + +/** + * A result contains details of a document matching a search query. + * @typedef {Object} lunr.Index~Result + * @property {string} ref - The reference of the document this result represents. + * @property {number} score - A number between 0 and 1 representing how similar this document is to the query. + * @property {lunr.MatchData} matchData - Contains metadata about this match including which term(s) caused the match. + */ + +/** + * Although lunr provides the ability to create queries using lunr.Query, it also provides a simple + * query language which itself is parsed into an instance of lunr.Query. + * + * For programmatically building queries it is advised to directly use lunr.Query, the query language + * is best used for human entered text rather than program generated text. + * + * At its simplest queries can just be a single term, e.g. `hello`, multiple terms are also supported + * and will be combined with OR, e.g `hello world` will match documents that contain either 'hello' + * or 'world', though those that contain both will rank higher in the results. + * + * Wildcards can be included in terms to match one or more unspecified characters, these wildcards can + * be inserted anywhere within the term, and more than one wildcard can exist in a single term. Adding + * wildcards will increase the number of documents that will be found but can also have a negative + * impact on query performance, especially with wildcards at the beginning of a term. + * + * Terms can be restricted to specific fields, e.g. `title:hello`, only documents with the term + * hello in the title field will match this query. Using a field not present in the index will lead + * to an error being thrown. + * + * Modifiers can also be added to terms, lunr supports edit distance and boost modifiers on terms. A term + * boost will make documents matching that term score higher, e.g. `foo^5`. Edit distance is also supported + * to provide fuzzy matching, e.g. 'hello~2' will match documents with hello with an edit distance of 2. + * Avoid large values for edit distance to improve query performance. + * + * Each term also supports a presence modifier. By default a term's presence in document is optional, however + * this can be changed to either required or prohibited. For a term's presence to be required in a document the + * term should be prefixed with a '+', e.g. `+foo bar` is a search for documents that must contain 'foo' and + * optionally contain 'bar'. Conversely a leading '-' sets the terms presence to prohibited, i.e. it must not + * appear in a document, e.g. `-foo bar` is a search for documents that do not contain 'foo' but may contain 'bar'. + * + * To escape special characters the backslash character '\' can be used, this allows searches to include + * characters that would normally be considered modifiers, e.g. `foo\~2` will search for a term "foo~2" instead + * of attempting to apply a boost of 2 to the search term "foo". + * + * @typedef {string} lunr.Index~QueryString + * @example Simple single term query + * hello + * @example Multiple term query + * hello world + * @example term scoped to a field + * title:hello + * @example term with a boost of 10 + * hello^10 + * @example term with an edit distance of 2 + * hello~2 + * @example terms with presence modifiers + * -foo +bar baz + */ + +/** + * Performs a search against the index using lunr query syntax. + * + * Results will be returned sorted by their score, the most relevant results + * will be returned first. For details on how the score is calculated, please see + * the {@link https://lunrjs.com/guides/searching.html#scoring|guide}. + * + * For more programmatic querying use lunr.Index#query. + * + * @param {lunr.Index~QueryString} queryString - A string containing a lunr query. + * @throws {lunr.QueryParseError} If the passed query string cannot be parsed. + * @returns {lunr.Index~Result[]} + */ +lunr.Index.prototype.search = function (queryString) { + return this.query(function (query) { + var parser = new lunr.QueryParser(queryString, query) + parser.parse() + }) +} + +/** + * A query builder callback provides a query object to be used to express + * the query to perform on the index. + * + * @callback lunr.Index~queryBuilder + * @param {lunr.Query} query - The query object to build up. + * @this lunr.Query + */ + +/** + * Performs a query against the index using the yielded lunr.Query object. + * + * If performing programmatic queries against the index, this method is preferred + * over lunr.Index#search so as to avoid the additional query parsing overhead. + * + * A query object is yielded to the supplied function which should be used to + * express the query to be run against the index. + * + * Note that although this function takes a callback parameter it is _not_ an + * asynchronous operation, the callback is just yielded a query object to be + * customized. + * + * @param {lunr.Index~queryBuilder} fn - A function that is used to build the query. + * @returns {lunr.Index~Result[]} + */ +lunr.Index.prototype.query = function (fn) { + // for each query clause + // * process terms + // * expand terms from token set + // * find matching documents and metadata + // * get document vectors + // * score documents + + var query = new lunr.Query(this.fields), + matchingFields = Object.create(null), + queryVectors = Object.create(null), + termFieldCache = Object.create(null), + requiredMatches = Object.create(null), + prohibitedMatches = Object.create(null) + + /* + * To support field level boosts a query vector is created per + * field. An empty vector is eagerly created to support negated + * queries. + */ + for (var i = 0; i < this.fields.length; i++) { + queryVectors[this.fields[i]] = new lunr.Vector + } + + fn.call(query, query) + + for (var i = 0; i < query.clauses.length; i++) { + /* + * Unless the pipeline has been disabled for this term, which is + * the case for terms with wildcards, we need to pass the clause + * term through the search pipeline. A pipeline returns an array + * of processed terms. Pipeline functions may expand the passed + * term, which means we may end up performing multiple index lookups + * for a single query term. + */ + var clause = query.clauses[i], + terms = null, + clauseMatches = lunr.Set.empty + + if (clause.usePipeline) { + terms = this.pipeline.runString(clause.term, { + fields: clause.fields + }) + } else { + terms = [clause.term] + } + + for (var m = 0; m < terms.length; m++) { + var term = terms[m] + + /* + * Each term returned from the pipeline needs to use the same query + * clause object, e.g. the same boost and or edit distance. The + * simplest way to do this is to re-use the clause object but mutate + * its term property. + */ + clause.term = term + + /* + * From the term in the clause we create a token set which will then + * be used to intersect the indexes token set to get a list of terms + * to lookup in the inverted index + */ + var termTokenSet = lunr.TokenSet.fromClause(clause), + expandedTerms = this.tokenSet.intersect(termTokenSet).toArray() + + /* + * If a term marked as required does not exist in the tokenSet it is + * impossible for the search to return any matches. We set all the field + * scoped required matches set to empty and stop examining any further + * clauses. + */ + if (expandedTerms.length === 0 && clause.presence === lunr.Query.presence.REQUIRED) { + for (var k = 0; k < clause.fields.length; k++) { + var field = clause.fields[k] + requiredMatches[field] = lunr.Set.empty + } + + break + } + + for (var j = 0; j < expandedTerms.length; j++) { + /* + * For each term get the posting and termIndex, this is required for + * building the query vector. + */ + var expandedTerm = expandedTerms[j], + posting = this.invertedIndex[expandedTerm], + termIndex = posting._index + + for (var k = 0; k < clause.fields.length; k++) { + /* + * For each field that this query term is scoped by (by default + * all fields are in scope) we need to get all the document refs + * that have this term in that field. + * + * The posting is the entry in the invertedIndex for the matching + * term from above. + */ + var field = clause.fields[k], + fieldPosting = posting[field], + matchingDocumentRefs = Object.keys(fieldPosting), + termField = expandedTerm + "/" + field, + matchingDocumentsSet = new lunr.Set(matchingDocumentRefs) + + /* + * if the presence of this term is required ensure that the matching + * documents are added to the set of required matches for this clause. + * + */ + if (clause.presence == lunr.Query.presence.REQUIRED) { + clauseMatches = clauseMatches.union(matchingDocumentsSet) + + if (requiredMatches[field] === undefined) { + requiredMatches[field] = lunr.Set.complete + } + } + + /* + * if the presence of this term is prohibited ensure that the matching + * documents are added to the set of prohibited matches for this field, + * creating that set if it does not yet exist. + */ + if (clause.presence == lunr.Query.presence.PROHIBITED) { + if (prohibitedMatches[field] === undefined) { + prohibitedMatches[field] = lunr.Set.empty + } + + prohibitedMatches[field] = prohibitedMatches[field].union(matchingDocumentsSet) + + /* + * Prohibited matches should not be part of the query vector used for + * similarity scoring and no metadata should be extracted so we continue + * to the next field + */ + continue + } + + /* + * The query field vector is populated using the termIndex found for + * the term and a unit value with the appropriate boost applied. + * Using upsert because there could already be an entry in the vector + * for the term we are working with. In that case we just add the scores + * together. + */ + queryVectors[field].upsert(termIndex, clause.boost, function (a, b) { return a + b }) + + /** + * If we've already seen this term, field combo then we've already collected + * the matching documents and metadata, no need to go through all that again + */ + if (termFieldCache[termField]) { + continue + } + + for (var l = 0; l < matchingDocumentRefs.length; l++) { + /* + * All metadata for this term/field/document triple + * are then extracted and collected into an instance + * of lunr.MatchData ready to be returned in the query + * results + */ + var matchingDocumentRef = matchingDocumentRefs[l], + matchingFieldRef = new lunr.FieldRef (matchingDocumentRef, field), + metadata = fieldPosting[matchingDocumentRef], + fieldMatch + + if ((fieldMatch = matchingFields[matchingFieldRef]) === undefined) { + matchingFields[matchingFieldRef] = new lunr.MatchData (expandedTerm, field, metadata) + } else { + fieldMatch.add(expandedTerm, field, metadata) + } + + } + + termFieldCache[termField] = true + } + } + } + + /** + * If the presence was required we need to update the requiredMatches field sets. + * We do this after all fields for the term have collected their matches because + * the clause terms presence is required in _any_ of the fields not _all_ of the + * fields. + */ + if (clause.presence === lunr.Query.presence.REQUIRED) { + for (var k = 0; k < clause.fields.length; k++) { + var field = clause.fields[k] + requiredMatches[field] = requiredMatches[field].intersect(clauseMatches) + } + } + } + + /** + * Need to combine the field scoped required and prohibited + * matching documents into a global set of required and prohibited + * matches + */ + var allRequiredMatches = lunr.Set.complete, + allProhibitedMatches = lunr.Set.empty + + for (var i = 0; i < this.fields.length; i++) { + var field = this.fields[i] + + if (requiredMatches[field]) { + allRequiredMatches = allRequiredMatches.intersect(requiredMatches[field]) + } + + if (prohibitedMatches[field]) { + allProhibitedMatches = allProhibitedMatches.union(prohibitedMatches[field]) + } + } + + var matchingFieldRefs = Object.keys(matchingFields), + results = [], + matches = Object.create(null) + + /* + * If the query is negated (contains only prohibited terms) + * we need to get _all_ fieldRefs currently existing in the + * index. This is only done when we know that the query is + * entirely prohibited terms to avoid any cost of getting all + * fieldRefs unnecessarily. + * + * Additionally, blank MatchData must be created to correctly + * populate the results. + */ + if (query.isNegated()) { + matchingFieldRefs = Object.keys(this.fieldVectors) + + for (var i = 0; i < matchingFieldRefs.length; i++) { + var matchingFieldRef = matchingFieldRefs[i] + var fieldRef = lunr.FieldRef.fromString(matchingFieldRef) + matchingFields[matchingFieldRef] = new lunr.MatchData + } + } + + for (var i = 0; i < matchingFieldRefs.length; i++) { + /* + * Currently we have document fields that match the query, but we + * need to return documents. The matchData and scores are combined + * from multiple fields belonging to the same document. + * + * Scores are calculated by field, using the query vectors created + * above, and combined into a final document score using addition. + */ + var fieldRef = lunr.FieldRef.fromString(matchingFieldRefs[i]), + docRef = fieldRef.docRef + + if (!allRequiredMatches.contains(docRef)) { + continue + } + + if (allProhibitedMatches.contains(docRef)) { + continue + } + + var fieldVector = this.fieldVectors[fieldRef], + score = queryVectors[fieldRef.fieldName].similarity(fieldVector), + docMatch + + if ((docMatch = matches[docRef]) !== undefined) { + docMatch.score += score + docMatch.matchData.combine(matchingFields[fieldRef]) + } else { + var match = { + ref: docRef, + score: score, + matchData: matchingFields[fieldRef] + } + matches[docRef] = match + results.push(match) + } + } + + /* + * Sort the results objects by score, highest first. + */ + return results.sort(function (a, b) { + return b.score - a.score + }) +} + +/** + * Prepares the index for JSON serialization. + * + * The schema for this JSON blob will be described in a + * separate JSON schema file. + * + * @returns {Object} + */ +lunr.Index.prototype.toJSON = function () { + var invertedIndex = Object.keys(this.invertedIndex) + .sort() + .map(function (term) { + return [term, this.invertedIndex[term]] + }, this) + + var fieldVectors = Object.keys(this.fieldVectors) + .map(function (ref) { + return [ref, this.fieldVectors[ref].toJSON()] + }, this) + + return { + version: lunr.version, + fields: this.fields, + fieldVectors: fieldVectors, + invertedIndex: invertedIndex, + pipeline: this.pipeline.toJSON() + } +} + +/** + * Loads a previously serialized lunr.Index + * + * @param {Object} serializedIndex - A previously serialized lunr.Index + * @returns {lunr.Index} + */ +lunr.Index.load = function (serializedIndex) { + var attrs = {}, + fieldVectors = {}, + serializedVectors = serializedIndex.fieldVectors, + invertedIndex = Object.create(null), + serializedInvertedIndex = serializedIndex.invertedIndex, + tokenSetBuilder = new lunr.TokenSet.Builder, + pipeline = lunr.Pipeline.load(serializedIndex.pipeline) + + if (serializedIndex.version != lunr.version) { + lunr.utils.warn("Version mismatch when loading serialised index. Current version of lunr '" + lunr.version + "' does not match serialized index '" + serializedIndex.version + "'") + } + + for (var i = 0; i < serializedVectors.length; i++) { + var tuple = serializedVectors[i], + ref = tuple[0], + elements = tuple[1] + + fieldVectors[ref] = new lunr.Vector(elements) + } + + for (var i = 0; i < serializedInvertedIndex.length; i++) { + var tuple = serializedInvertedIndex[i], + term = tuple[0], + posting = tuple[1] + + tokenSetBuilder.insert(term) + invertedIndex[term] = posting + } + + tokenSetBuilder.finish() + + attrs.fields = serializedIndex.fields + + attrs.fieldVectors = fieldVectors + attrs.invertedIndex = invertedIndex + attrs.tokenSet = tokenSetBuilder.root + attrs.pipeline = pipeline + + return new lunr.Index(attrs) +} +/*! + * lunr.Builder + * Copyright (C) 2020 Oliver Nightingale + */ + +/** + * lunr.Builder performs indexing on a set of documents and + * returns instances of lunr.Index ready for querying. + * + * All configuration of the index is done via the builder, the + * fields to index, the document reference, the text processing + * pipeline and document scoring parameters are all set on the + * builder before indexing. + * + * @constructor + * @property {string} _ref - Internal reference to the document reference field. + * @property {string[]} _fields - Internal reference to the document fields to index. + * @property {object} invertedIndex - The inverted index maps terms to document fields. + * @property {object} documentTermFrequencies - Keeps track of document term frequencies. + * @property {object} documentLengths - Keeps track of the length of documents added to the index. + * @property {lunr.tokenizer} tokenizer - Function for splitting strings into tokens for indexing. + * @property {lunr.Pipeline} pipeline - The pipeline performs text processing on tokens before indexing. + * @property {lunr.Pipeline} searchPipeline - A pipeline for processing search terms before querying the index. + * @property {number} documentCount - Keeps track of the total number of documents indexed. + * @property {number} _b - A parameter to control field length normalization, setting this to 0 disabled normalization, 1 fully normalizes field lengths, the default value is 0.75. + * @property {number} _k1 - A parameter to control how quickly an increase in term frequency results in term frequency saturation, the default value is 1.2. + * @property {number} termIndex - A counter incremented for each unique term, used to identify a terms position in the vector space. + * @property {array} metadataWhitelist - A list of metadata keys that have been whitelisted for entry in the index. + */ +lunr.Builder = function () { + this._ref = "id" + this._fields = Object.create(null) + this._documents = Object.create(null) + this.invertedIndex = Object.create(null) + this.fieldTermFrequencies = {} + this.fieldLengths = {} + this.tokenizer = lunr.tokenizer + this.pipeline = new lunr.Pipeline + this.searchPipeline = new lunr.Pipeline + this.documentCount = 0 + this._b = 0.75 + this._k1 = 1.2 + this.termIndex = 0 + this.metadataWhitelist = [] +} + +/** + * Sets the document field used as the document reference. Every document must have this field. + * The type of this field in the document should be a string, if it is not a string it will be + * coerced into a string by calling toString. + * + * The default ref is 'id'. + * + * The ref should _not_ be changed during indexing, it should be set before any documents are + * added to the index. Changing it during indexing can lead to inconsistent results. + * + * @param {string} ref - The name of the reference field in the document. + */ +lunr.Builder.prototype.ref = function (ref) { + this._ref = ref +} + +/** + * A function that is used to extract a field from a document. + * + * Lunr expects a field to be at the top level of a document, if however the field + * is deeply nested within a document an extractor function can be used to extract + * the right field for indexing. + * + * @callback fieldExtractor + * @param {object} doc - The document being added to the index. + * @returns {?(string|object|object[])} obj - The object that will be indexed for this field. + * @example Extracting a nested field + * function (doc) { return doc.nested.field } + */ + +/** + * Adds a field to the list of document fields that will be indexed. Every document being + * indexed should have this field. Null values for this field in indexed documents will + * not cause errors but will limit the chance of that document being retrieved by searches. + * + * All fields should be added before adding documents to the index. Adding fields after + * a document has been indexed will have no effect on already indexed documents. + * + * Fields can be boosted at build time. This allows terms within that field to have more + * importance when ranking search results. Use a field boost to specify that matches within + * one field are more important than other fields. + * + * @param {string} fieldName - The name of a field to index in all documents. + * @param {object} attributes - Optional attributes associated with this field. + * @param {number} [attributes.boost=1] - Boost applied to all terms within this field. + * @param {fieldExtractor} [attributes.extractor] - Function to extract a field from a document. + * @throws {RangeError} fieldName cannot contain unsupported characters '/' + */ +lunr.Builder.prototype.field = function (fieldName, attributes) { + if (/\//.test(fieldName)) { + throw new RangeError ("Field '" + fieldName + "' contains illegal character '/'") + } + + this._fields[fieldName] = attributes || {} +} + +/** + * A parameter to tune the amount of field length normalisation that is applied when + * calculating relevance scores. A value of 0 will completely disable any normalisation + * and a value of 1 will fully normalise field lengths. The default is 0.75. Values of b + * will be clamped to the range 0 - 1. + * + * @param {number} number - The value to set for this tuning parameter. + */ +lunr.Builder.prototype.b = function (number) { + if (number < 0) { + this._b = 0 + } else if (number > 1) { + this._b = 1 + } else { + this._b = number + } +} + +/** + * A parameter that controls the speed at which a rise in term frequency results in term + * frequency saturation. The default value is 1.2. Setting this to a higher value will give + * slower saturation levels, a lower value will result in quicker saturation. + * + * @param {number} number - The value to set for this tuning parameter. + */ +lunr.Builder.prototype.k1 = function (number) { + this._k1 = number +} + +/** + * Adds a document to the index. + * + * Before adding fields to the index the index should have been fully setup, with the document + * ref and all fields to index already having been specified. + * + * The document must have a field name as specified by the ref (by default this is 'id') and + * it should have all fields defined for indexing, though null or undefined values will not + * cause errors. + * + * Entire documents can be boosted at build time. Applying a boost to a document indicates that + * this document should rank higher in search results than other documents. + * + * @param {object} doc - The document to add to the index. + * @param {object} attributes - Optional attributes associated with this document. + * @param {number} [attributes.boost=1] - Boost applied to all terms within this document. + */ +lunr.Builder.prototype.add = function (doc, attributes) { + var docRef = doc[this._ref], + fields = Object.keys(this._fields) + + this._documents[docRef] = attributes || {} + this.documentCount += 1 + + for (var i = 0; i < fields.length; i++) { + var fieldName = fields[i], + extractor = this._fields[fieldName].extractor, + field = extractor ? extractor(doc) : doc[fieldName], + tokens = this.tokenizer(field, { + fields: [fieldName] + }), + terms = this.pipeline.run(tokens), + fieldRef = new lunr.FieldRef (docRef, fieldName), + fieldTerms = Object.create(null) + + this.fieldTermFrequencies[fieldRef] = fieldTerms + this.fieldLengths[fieldRef] = 0 + + // store the length of this field for this document + this.fieldLengths[fieldRef] += terms.length + + // calculate term frequencies for this field + for (var j = 0; j < terms.length; j++) { + var term = terms[j] + + if (fieldTerms[term] == undefined) { + fieldTerms[term] = 0 + } + + fieldTerms[term] += 1 + + // add to inverted index + // create an initial posting if one doesn't exist + if (this.invertedIndex[term] == undefined) { + var posting = Object.create(null) + posting["_index"] = this.termIndex + this.termIndex += 1 + + for (var k = 0; k < fields.length; k++) { + posting[fields[k]] = Object.create(null) + } + + this.invertedIndex[term] = posting + } + + // add an entry for this term/fieldName/docRef to the invertedIndex + if (this.invertedIndex[term][fieldName][docRef] == undefined) { + this.invertedIndex[term][fieldName][docRef] = Object.create(null) + } + + // store all whitelisted metadata about this token in the + // inverted index + for (var l = 0; l < this.metadataWhitelist.length; l++) { + var metadataKey = this.metadataWhitelist[l], + metadata = term.metadata[metadataKey] + + if (this.invertedIndex[term][fieldName][docRef][metadataKey] == undefined) { + this.invertedIndex[term][fieldName][docRef][metadataKey] = [] + } + + this.invertedIndex[term][fieldName][docRef][metadataKey].push(metadata) + } + } + + } +} + +/** + * Calculates the average document length for this index + * + * @private + */ +lunr.Builder.prototype.calculateAverageFieldLengths = function () { + + var fieldRefs = Object.keys(this.fieldLengths), + numberOfFields = fieldRefs.length, + accumulator = {}, + documentsWithField = {} + + for (var i = 0; i < numberOfFields; i++) { + var fieldRef = lunr.FieldRef.fromString(fieldRefs[i]), + field = fieldRef.fieldName + + documentsWithField[field] || (documentsWithField[field] = 0) + documentsWithField[field] += 1 + + accumulator[field] || (accumulator[field] = 0) + accumulator[field] += this.fieldLengths[fieldRef] + } + + var fields = Object.keys(this._fields) + + for (var i = 0; i < fields.length; i++) { + var fieldName = fields[i] + accumulator[fieldName] = accumulator[fieldName] / documentsWithField[fieldName] + } + + this.averageFieldLength = accumulator +} + +/** + * Builds a vector space model of every document using lunr.Vector + * + * @private + */ +lunr.Builder.prototype.createFieldVectors = function () { + var fieldVectors = {}, + fieldRefs = Object.keys(this.fieldTermFrequencies), + fieldRefsLength = fieldRefs.length, + termIdfCache = Object.create(null) + + for (var i = 0; i < fieldRefsLength; i++) { + var fieldRef = lunr.FieldRef.fromString(fieldRefs[i]), + fieldName = fieldRef.fieldName, + fieldLength = this.fieldLengths[fieldRef], + fieldVector = new lunr.Vector, + termFrequencies = this.fieldTermFrequencies[fieldRef], + terms = Object.keys(termFrequencies), + termsLength = terms.length + + + var fieldBoost = this._fields[fieldName].boost || 1, + docBoost = this._documents[fieldRef.docRef].boost || 1 + + for (var j = 0; j < termsLength; j++) { + var term = terms[j], + tf = termFrequencies[term], + termIndex = this.invertedIndex[term]._index, + idf, score, scoreWithPrecision + + if (termIdfCache[term] === undefined) { + idf = lunr.idf(this.invertedIndex[term], this.documentCount) + termIdfCache[term] = idf + } else { + idf = termIdfCache[term] + } + + score = idf * ((this._k1 + 1) * tf) / (this._k1 * (1 - this._b + this._b * (fieldLength / this.averageFieldLength[fieldName])) + tf) + score *= fieldBoost + score *= docBoost + scoreWithPrecision = Math.round(score * 1000) / 1000 + // Converts 1.23456789 to 1.234. + // Reducing the precision so that the vectors take up less + // space when serialised. Doing it now so that they behave + // the same before and after serialisation. Also, this is + // the fastest approach to reducing a number's precision in + // JavaScript. + + fieldVector.insert(termIndex, scoreWithPrecision) + } + + fieldVectors[fieldRef] = fieldVector + } + + this.fieldVectors = fieldVectors +} + +/** + * Creates a token set of all tokens in the index using lunr.TokenSet + * + * @private + */ +lunr.Builder.prototype.createTokenSet = function () { + this.tokenSet = lunr.TokenSet.fromArray( + Object.keys(this.invertedIndex).sort() + ) +} + +/** + * Builds the index, creating an instance of lunr.Index. + * + * This completes the indexing process and should only be called + * once all documents have been added to the index. + * + * @returns {lunr.Index} + */ +lunr.Builder.prototype.build = function () { + this.calculateAverageFieldLengths() + this.createFieldVectors() + this.createTokenSet() + + return new lunr.Index({ + invertedIndex: this.invertedIndex, + fieldVectors: this.fieldVectors, + tokenSet: this.tokenSet, + fields: Object.keys(this._fields), + pipeline: this.searchPipeline + }) +} + +/** + * Applies a plugin to the index builder. + * + * A plugin is a function that is called with the index builder as its context. + * Plugins can be used to customise or extend the behaviour of the index + * in some way. A plugin is just a function, that encapsulated the custom + * behaviour that should be applied when building the index. + * + * The plugin function will be called with the index builder as its argument, additional + * arguments can also be passed when calling use. The function will be called + * with the index builder as its context. + * + * @param {Function} plugin The plugin to apply. + */ +lunr.Builder.prototype.use = function (fn) { + var args = Array.prototype.slice.call(arguments, 1) + args.unshift(this) + fn.apply(this, args) +} +/** + * Contains and collects metadata about a matching document. + * A single instance of lunr.MatchData is returned as part of every + * lunr.Index~Result. + * + * @constructor + * @param {string} term - The term this match data is associated with + * @param {string} field - The field in which the term was found + * @param {object} metadata - The metadata recorded about this term in this field + * @property {object} metadata - A cloned collection of metadata associated with this document. + * @see {@link lunr.Index~Result} + */ +lunr.MatchData = function (term, field, metadata) { + var clonedMetadata = Object.create(null), + metadataKeys = Object.keys(metadata || {}) + + // Cloning the metadata to prevent the original + // being mutated during match data combination. + // Metadata is kept in an array within the inverted + // index so cloning the data can be done with + // Array#slice + for (var i = 0; i < metadataKeys.length; i++) { + var key = metadataKeys[i] + clonedMetadata[key] = metadata[key].slice() + } + + this.metadata = Object.create(null) + + if (term !== undefined) { + this.metadata[term] = Object.create(null) + this.metadata[term][field] = clonedMetadata + } +} + +/** + * An instance of lunr.MatchData will be created for every term that matches a + * document. However only one instance is required in a lunr.Index~Result. This + * method combines metadata from another instance of lunr.MatchData with this + * objects metadata. + * + * @param {lunr.MatchData} otherMatchData - Another instance of match data to merge with this one. + * @see {@link lunr.Index~Result} + */ +lunr.MatchData.prototype.combine = function (otherMatchData) { + var terms = Object.keys(otherMatchData.metadata) + + for (var i = 0; i < terms.length; i++) { + var term = terms[i], + fields = Object.keys(otherMatchData.metadata[term]) + + if (this.metadata[term] == undefined) { + this.metadata[term] = Object.create(null) + } + + for (var j = 0; j < fields.length; j++) { + var field = fields[j], + keys = Object.keys(otherMatchData.metadata[term][field]) + + if (this.metadata[term][field] == undefined) { + this.metadata[term][field] = Object.create(null) + } + + for (var k = 0; k < keys.length; k++) { + var key = keys[k] + + if (this.metadata[term][field][key] == undefined) { + this.metadata[term][field][key] = otherMatchData.metadata[term][field][key] + } else { + this.metadata[term][field][key] = this.metadata[term][field][key].concat(otherMatchData.metadata[term][field][key]) + } + + } + } + } +} + +/** + * Add metadata for a term/field pair to this instance of match data. + * + * @param {string} term - The term this match data is associated with + * @param {string} field - The field in which the term was found + * @param {object} metadata - The metadata recorded about this term in this field + */ +lunr.MatchData.prototype.add = function (term, field, metadata) { + if (!(term in this.metadata)) { + this.metadata[term] = Object.create(null) + this.metadata[term][field] = metadata + return + } + + if (!(field in this.metadata[term])) { + this.metadata[term][field] = metadata + return + } + + var metadataKeys = Object.keys(metadata) + + for (var i = 0; i < metadataKeys.length; i++) { + var key = metadataKeys[i] + + if (key in this.metadata[term][field]) { + this.metadata[term][field][key] = this.metadata[term][field][key].concat(metadata[key]) + } else { + this.metadata[term][field][key] = metadata[key] + } + } +} +/** + * A lunr.Query provides a programmatic way of defining queries to be performed + * against a {@link lunr.Index}. + * + * Prefer constructing a lunr.Query using the {@link lunr.Index#query} method + * so the query object is pre-initialized with the right index fields. + * + * @constructor + * @property {lunr.Query~Clause[]} clauses - An array of query clauses. + * @property {string[]} allFields - An array of all available fields in a lunr.Index. + */ +lunr.Query = function (allFields) { + this.clauses = [] + this.allFields = allFields +} + +/** + * Constants for indicating what kind of automatic wildcard insertion will be used when constructing a query clause. + * + * This allows wildcards to be added to the beginning and end of a term without having to manually do any string + * concatenation. + * + * The wildcard constants can be bitwise combined to select both leading and trailing wildcards. + * + * @constant + * @default + * @property {number} wildcard.NONE - The term will have no wildcards inserted, this is the default behaviour + * @property {number} wildcard.LEADING - Prepend the term with a wildcard, unless a leading wildcard already exists + * @property {number} wildcard.TRAILING - Append a wildcard to the term, unless a trailing wildcard already exists + * @see lunr.Query~Clause + * @see lunr.Query#clause + * @see lunr.Query#term + * @example query term with trailing wildcard + * query.term('foo', { wildcard: lunr.Query.wildcard.TRAILING }) + * @example query term with leading and trailing wildcard + * query.term('foo', { + * wildcard: lunr.Query.wildcard.LEADING | lunr.Query.wildcard.TRAILING + * }) + */ + +lunr.Query.wildcard = new String ("*") +lunr.Query.wildcard.NONE = 0 +lunr.Query.wildcard.LEADING = 1 +lunr.Query.wildcard.TRAILING = 2 + +/** + * Constants for indicating what kind of presence a term must have in matching documents. + * + * @constant + * @enum {number} + * @see lunr.Query~Clause + * @see lunr.Query#clause + * @see lunr.Query#term + * @example query term with required presence + * query.term('foo', { presence: lunr.Query.presence.REQUIRED }) + */ +lunr.Query.presence = { + /** + * Term's presence in a document is optional, this is the default value. + */ + OPTIONAL: 1, + + /** + * Term's presence in a document is required, documents that do not contain + * this term will not be returned. + */ + REQUIRED: 2, + + /** + * Term's presence in a document is prohibited, documents that do contain + * this term will not be returned. + */ + PROHIBITED: 3 +} + +/** + * A single clause in a {@link lunr.Query} contains a term and details on how to + * match that term against a {@link lunr.Index}. + * + * @typedef {Object} lunr.Query~Clause + * @property {string[]} fields - The fields in an index this clause should be matched against. + * @property {number} [boost=1] - Any boost that should be applied when matching this clause. + * @property {number} [editDistance] - Whether the term should have fuzzy matching applied, and how fuzzy the match should be. + * @property {boolean} [usePipeline] - Whether the term should be passed through the search pipeline. + * @property {number} [wildcard=lunr.Query.wildcard.NONE] - Whether the term should have wildcards appended or prepended. + * @property {number} [presence=lunr.Query.presence.OPTIONAL] - The terms presence in any matching documents. + */ + +/** + * Adds a {@link lunr.Query~Clause} to this query. + * + * Unless the clause contains the fields to be matched all fields will be matched. In addition + * a default boost of 1 is applied to the clause. + * + * @param {lunr.Query~Clause} clause - The clause to add to this query. + * @see lunr.Query~Clause + * @returns {lunr.Query} + */ +lunr.Query.prototype.clause = function (clause) { + if (!('fields' in clause)) { + clause.fields = this.allFields + } + + if (!('boost' in clause)) { + clause.boost = 1 + } + + if (!('usePipeline' in clause)) { + clause.usePipeline = true + } + + if (!('wildcard' in clause)) { + clause.wildcard = lunr.Query.wildcard.NONE + } + + if ((clause.wildcard & lunr.Query.wildcard.LEADING) && (clause.term.charAt(0) != lunr.Query.wildcard)) { + clause.term = "*" + clause.term + } + + if ((clause.wildcard & lunr.Query.wildcard.TRAILING) && (clause.term.slice(-1) != lunr.Query.wildcard)) { + clause.term = "" + clause.term + "*" + } + + if (!('presence' in clause)) { + clause.presence = lunr.Query.presence.OPTIONAL + } + + this.clauses.push(clause) + + return this +} + +/** + * A negated query is one in which every clause has a presence of + * prohibited. These queries require some special processing to return + * the expected results. + * + * @returns boolean + */ +lunr.Query.prototype.isNegated = function () { + for (var i = 0; i < this.clauses.length; i++) { + if (this.clauses[i].presence != lunr.Query.presence.PROHIBITED) { + return false + } + } + + return true +} + +/** + * Adds a term to the current query, under the covers this will create a {@link lunr.Query~Clause} + * to the list of clauses that make up this query. + * + * The term is used as is, i.e. no tokenization will be performed by this method. Instead conversion + * to a token or token-like string should be done before calling this method. + * + * The term will be converted to a string by calling `toString`. Multiple terms can be passed as an + * array, each term in the array will share the same options. + * + * @param {object|object[]} term - The term(s) to add to the query. + * @param {object} [options] - Any additional properties to add to the query clause. + * @returns {lunr.Query} + * @see lunr.Query#clause + * @see lunr.Query~Clause + * @example adding a single term to a query + * query.term("foo") + * @example adding a single term to a query and specifying search fields, term boost and automatic trailing wildcard + * query.term("foo", { + * fields: ["title"], + * boost: 10, + * wildcard: lunr.Query.wildcard.TRAILING + * }) + * @example using lunr.tokenizer to convert a string to tokens before using them as terms + * query.term(lunr.tokenizer("foo bar")) + */ +lunr.Query.prototype.term = function (term, options) { + if (Array.isArray(term)) { + term.forEach(function (t) { this.term(t, lunr.utils.clone(options)) }, this) + return this + } + + var clause = options || {} + clause.term = term.toString() + + this.clause(clause) + + return this +} +lunr.QueryParseError = function (message, start, end) { + this.name = "QueryParseError" + this.message = message + this.start = start + this.end = end +} + +lunr.QueryParseError.prototype = new Error +lunr.QueryLexer = function (str) { + this.lexemes = [] + this.str = str + this.length = str.length + this.pos = 0 + this.start = 0 + this.escapeCharPositions = [] +} + +lunr.QueryLexer.prototype.run = function () { + var state = lunr.QueryLexer.lexText + + while (state) { + state = state(this) + } +} + +lunr.QueryLexer.prototype.sliceString = function () { + var subSlices = [], + sliceStart = this.start, + sliceEnd = this.pos + + for (var i = 0; i < this.escapeCharPositions.length; i++) { + sliceEnd = this.escapeCharPositions[i] + subSlices.push(this.str.slice(sliceStart, sliceEnd)) + sliceStart = sliceEnd + 1 + } + + subSlices.push(this.str.slice(sliceStart, this.pos)) + this.escapeCharPositions.length = 0 + + return subSlices.join('') +} + +lunr.QueryLexer.prototype.emit = function (type) { + this.lexemes.push({ + type: type, + str: this.sliceString(), + start: this.start, + end: this.pos + }) + + this.start = this.pos +} + +lunr.QueryLexer.prototype.escapeCharacter = function () { + this.escapeCharPositions.push(this.pos - 1) + this.pos += 1 +} + +lunr.QueryLexer.prototype.next = function () { + if (this.pos >= this.length) { + return lunr.QueryLexer.EOS + } + + var char = this.str.charAt(this.pos) + this.pos += 1 + return char +} + +lunr.QueryLexer.prototype.width = function () { + return this.pos - this.start +} + +lunr.QueryLexer.prototype.ignore = function () { + if (this.start == this.pos) { + this.pos += 1 + } + + this.start = this.pos +} + +lunr.QueryLexer.prototype.backup = function () { + this.pos -= 1 +} + +lunr.QueryLexer.prototype.acceptDigitRun = function () { + var char, charCode + + do { + char = this.next() + charCode = char.charCodeAt(0) + } while (charCode > 47 && charCode < 58) + + if (char != lunr.QueryLexer.EOS) { + this.backup() + } +} + +lunr.QueryLexer.prototype.more = function () { + return this.pos < this.length +} + +lunr.QueryLexer.EOS = 'EOS' +lunr.QueryLexer.FIELD = 'FIELD' +lunr.QueryLexer.TERM = 'TERM' +lunr.QueryLexer.EDIT_DISTANCE = 'EDIT_DISTANCE' +lunr.QueryLexer.BOOST = 'BOOST' +lunr.QueryLexer.PRESENCE = 'PRESENCE' + +lunr.QueryLexer.lexField = function (lexer) { + lexer.backup() + lexer.emit(lunr.QueryLexer.FIELD) + lexer.ignore() + return lunr.QueryLexer.lexText +} + +lunr.QueryLexer.lexTerm = function (lexer) { + if (lexer.width() > 1) { + lexer.backup() + lexer.emit(lunr.QueryLexer.TERM) + } + + lexer.ignore() + + if (lexer.more()) { + return lunr.QueryLexer.lexText + } +} + +lunr.QueryLexer.lexEditDistance = function (lexer) { + lexer.ignore() + lexer.acceptDigitRun() + lexer.emit(lunr.QueryLexer.EDIT_DISTANCE) + return lunr.QueryLexer.lexText +} + +lunr.QueryLexer.lexBoost = function (lexer) { + lexer.ignore() + lexer.acceptDigitRun() + lexer.emit(lunr.QueryLexer.BOOST) + return lunr.QueryLexer.lexText +} + +lunr.QueryLexer.lexEOS = function (lexer) { + if (lexer.width() > 0) { + lexer.emit(lunr.QueryLexer.TERM) + } +} + +// This matches the separator used when tokenising fields +// within a document. These should match otherwise it is +// not possible to search for some tokens within a document. +// +// It is possible for the user to change the separator on the +// tokenizer so it _might_ clash with any other of the special +// characters already used within the search string, e.g. :. +// +// This means that it is possible to change the separator in +// such a way that makes some words unsearchable using a search +// string. +lunr.QueryLexer.termSeparator = lunr.tokenizer.separator + +lunr.QueryLexer.lexText = function (lexer) { + while (true) { + var char = lexer.next() + + if (char == lunr.QueryLexer.EOS) { + return lunr.QueryLexer.lexEOS + } + + // Escape character is '\' + if (char.charCodeAt(0) == 92) { + lexer.escapeCharacter() + continue + } + + if (char == ":") { + return lunr.QueryLexer.lexField + } + + if (char == "~") { + lexer.backup() + if (lexer.width() > 0) { + lexer.emit(lunr.QueryLexer.TERM) + } + return lunr.QueryLexer.lexEditDistance + } + + if (char == "^") { + lexer.backup() + if (lexer.width() > 0) { + lexer.emit(lunr.QueryLexer.TERM) + } + return lunr.QueryLexer.lexBoost + } + + // "+" indicates term presence is required + // checking for length to ensure that only + // leading "+" are considered + if (char == "+" && lexer.width() === 1) { + lexer.emit(lunr.QueryLexer.PRESENCE) + return lunr.QueryLexer.lexText + } + + // "-" indicates term presence is prohibited + // checking for length to ensure that only + // leading "-" are considered + if (char == "-" && lexer.width() === 1) { + lexer.emit(lunr.QueryLexer.PRESENCE) + return lunr.QueryLexer.lexText + } + + if (char.match(lunr.QueryLexer.termSeparator)) { + return lunr.QueryLexer.lexTerm + } + } +} + +lunr.QueryParser = function (str, query) { + this.lexer = new lunr.QueryLexer (str) + this.query = query + this.currentClause = {} + this.lexemeIdx = 0 +} + +lunr.QueryParser.prototype.parse = function () { + this.lexer.run() + this.lexemes = this.lexer.lexemes + + var state = lunr.QueryParser.parseClause + + while (state) { + state = state(this) + } + + return this.query +} + +lunr.QueryParser.prototype.peekLexeme = function () { + return this.lexemes[this.lexemeIdx] +} + +lunr.QueryParser.prototype.consumeLexeme = function () { + var lexeme = this.peekLexeme() + this.lexemeIdx += 1 + return lexeme +} + +lunr.QueryParser.prototype.nextClause = function () { + var completedClause = this.currentClause + this.query.clause(completedClause) + this.currentClause = {} +} + +lunr.QueryParser.parseClause = function (parser) { + var lexeme = parser.peekLexeme() + + if (lexeme == undefined) { + return + } + + switch (lexeme.type) { + case lunr.QueryLexer.PRESENCE: + return lunr.QueryParser.parsePresence + case lunr.QueryLexer.FIELD: + return lunr.QueryParser.parseField + case lunr.QueryLexer.TERM: + return lunr.QueryParser.parseTerm + default: + var errorMessage = "expected either a field or a term, found " + lexeme.type + + if (lexeme.str.length >= 1) { + errorMessage += " with value '" + lexeme.str + "'" + } + + throw new lunr.QueryParseError (errorMessage, lexeme.start, lexeme.end) + } +} + +lunr.QueryParser.parsePresence = function (parser) { + var lexeme = parser.consumeLexeme() + + if (lexeme == undefined) { + return + } + + switch (lexeme.str) { + case "-": + parser.currentClause.presence = lunr.Query.presence.PROHIBITED + break + case "+": + parser.currentClause.presence = lunr.Query.presence.REQUIRED + break + default: + var errorMessage = "unrecognised presence operator'" + lexeme.str + "'" + throw new lunr.QueryParseError (errorMessage, lexeme.start, lexeme.end) + } + + var nextLexeme = parser.peekLexeme() + + if (nextLexeme == undefined) { + var errorMessage = "expecting term or field, found nothing" + throw new lunr.QueryParseError (errorMessage, lexeme.start, lexeme.end) + } + + switch (nextLexeme.type) { + case lunr.QueryLexer.FIELD: + return lunr.QueryParser.parseField + case lunr.QueryLexer.TERM: + return lunr.QueryParser.parseTerm + default: + var errorMessage = "expecting term or field, found '" + nextLexeme.type + "'" + throw new lunr.QueryParseError (errorMessage, nextLexeme.start, nextLexeme.end) + } +} + +lunr.QueryParser.parseField = function (parser) { + var lexeme = parser.consumeLexeme() + + if (lexeme == undefined) { + return + } + + if (parser.query.allFields.indexOf(lexeme.str) == -1) { + var possibleFields = parser.query.allFields.map(function (f) { return "'" + f + "'" }).join(', '), + errorMessage = "unrecognised field '" + lexeme.str + "', possible fields: " + possibleFields + + throw new lunr.QueryParseError (errorMessage, lexeme.start, lexeme.end) + } + + parser.currentClause.fields = [lexeme.str] + + var nextLexeme = parser.peekLexeme() + + if (nextLexeme == undefined) { + var errorMessage = "expecting term, found nothing" + throw new lunr.QueryParseError (errorMessage, lexeme.start, lexeme.end) + } + + switch (nextLexeme.type) { + case lunr.QueryLexer.TERM: + return lunr.QueryParser.parseTerm + default: + var errorMessage = "expecting term, found '" + nextLexeme.type + "'" + throw new lunr.QueryParseError (errorMessage, nextLexeme.start, nextLexeme.end) + } +} + +lunr.QueryParser.parseTerm = function (parser) { + var lexeme = parser.consumeLexeme() + + if (lexeme == undefined) { + return + } + + parser.currentClause.term = lexeme.str.toLowerCase() + + if (lexeme.str.indexOf("*") != -1) { + parser.currentClause.usePipeline = false + } + + var nextLexeme = parser.peekLexeme() + + if (nextLexeme == undefined) { + parser.nextClause() + return + } + + switch (nextLexeme.type) { + case lunr.QueryLexer.TERM: + parser.nextClause() + return lunr.QueryParser.parseTerm + case lunr.QueryLexer.FIELD: + parser.nextClause() + return lunr.QueryParser.parseField + case lunr.QueryLexer.EDIT_DISTANCE: + return lunr.QueryParser.parseEditDistance + case lunr.QueryLexer.BOOST: + return lunr.QueryParser.parseBoost + case lunr.QueryLexer.PRESENCE: + parser.nextClause() + return lunr.QueryParser.parsePresence + default: + var errorMessage = "Unexpected lexeme type '" + nextLexeme.type + "'" + throw new lunr.QueryParseError (errorMessage, nextLexeme.start, nextLexeme.end) + } +} + +lunr.QueryParser.parseEditDistance = function (parser) { + var lexeme = parser.consumeLexeme() + + if (lexeme == undefined) { + return + } + + var editDistance = parseInt(lexeme.str, 10) + + if (isNaN(editDistance)) { + var errorMessage = "edit distance must be numeric" + throw new lunr.QueryParseError (errorMessage, lexeme.start, lexeme.end) + } + + parser.currentClause.editDistance = editDistance + + var nextLexeme = parser.peekLexeme() + + if (nextLexeme == undefined) { + parser.nextClause() + return + } + + switch (nextLexeme.type) { + case lunr.QueryLexer.TERM: + parser.nextClause() + return lunr.QueryParser.parseTerm + case lunr.QueryLexer.FIELD: + parser.nextClause() + return lunr.QueryParser.parseField + case lunr.QueryLexer.EDIT_DISTANCE: + return lunr.QueryParser.parseEditDistance + case lunr.QueryLexer.BOOST: + return lunr.QueryParser.parseBoost + case lunr.QueryLexer.PRESENCE: + parser.nextClause() + return lunr.QueryParser.parsePresence + default: + var errorMessage = "Unexpected lexeme type '" + nextLexeme.type + "'" + throw new lunr.QueryParseError (errorMessage, nextLexeme.start, nextLexeme.end) + } +} + +lunr.QueryParser.parseBoost = function (parser) { + var lexeme = parser.consumeLexeme() + + if (lexeme == undefined) { + return + } + + var boost = parseInt(lexeme.str, 10) + + if (isNaN(boost)) { + var errorMessage = "boost must be numeric" + throw new lunr.QueryParseError (errorMessage, lexeme.start, lexeme.end) + } + + parser.currentClause.boost = boost + + var nextLexeme = parser.peekLexeme() + + if (nextLexeme == undefined) { + parser.nextClause() + return + } + + switch (nextLexeme.type) { + case lunr.QueryLexer.TERM: + parser.nextClause() + return lunr.QueryParser.parseTerm + case lunr.QueryLexer.FIELD: + parser.nextClause() + return lunr.QueryParser.parseField + case lunr.QueryLexer.EDIT_DISTANCE: + return lunr.QueryParser.parseEditDistance + case lunr.QueryLexer.BOOST: + return lunr.QueryParser.parseBoost + case lunr.QueryLexer.PRESENCE: + parser.nextClause() + return lunr.QueryParser.parsePresence + default: + var errorMessage = "Unexpected lexeme type '" + nextLexeme.type + "'" + throw new lunr.QueryParseError (errorMessage, nextLexeme.start, nextLexeme.end) + } +} + + /** + * export the module via AMD, CommonJS or as a browser global + * Export code from https://github.com/umdjs/umd/blob/master/returnExports.js + */ + ;(function (root, factory) { + if (typeof define === 'function' && define.amd) { + // AMD. Register as an anonymous module. + define(factory) + } else if (typeof exports === 'object') { + /** + * Node. Does not work with strict CommonJS, but + * only CommonJS-like environments that support module.exports, + * like Node. + */ + module.exports = factory() + } else { + // Browser globals (root is window) + root.lunr = factory() + } + }(this, function () { + /** + * Just return a value to define the module export. + * This example returns an object, but the module + * can return a function as the exported value. + */ + return lunr + })) +})(); diff --git a/search/main.js b/search/main.js new file mode 100644 index 0000000..a5e469d --- /dev/null +++ b/search/main.js @@ -0,0 +1,109 @@ +function getSearchTermFromLocation() { + var sPageURL = window.location.search.substring(1); + var sURLVariables = sPageURL.split('&'); + for (var i = 0; i < sURLVariables.length; i++) { + var sParameterName = sURLVariables[i].split('='); + if (sParameterName[0] == 'q') { + return decodeURIComponent(sParameterName[1].replace(/\+/g, '%20')); + } + } +} + +function joinUrl (base, path) { + if (path.substring(0, 1) === "/") { + // path starts with `/`. Thus it is absolute. + return path; + } + if (base.substring(base.length-1) === "/") { + // base ends with `/` + return base + path; + } + return base + "/" + path; +} + +function escapeHtml (value) { + return value.replace(/&/g, '&') + .replace(/"/g, '"') + .replace(//g, '>'); +} + +function formatResult (location, title, summary) { + return ''; +} + +function displayResults (results) { + var search_results = document.getElementById("mkdocs-search-results"); + while (search_results.firstChild) { + search_results.removeChild(search_results.firstChild); + } + if (results.length > 0){ + for (var i=0; i < results.length; i++){ + var result = results[i]; + var html = formatResult(result.location, result.title, result.summary); + search_results.insertAdjacentHTML('beforeend', html); + } + } else { + var noResultsText = search_results.getAttribute('data-no-results-text'); + if (!noResultsText) { + noResultsText = "No results found"; + } + search_results.insertAdjacentHTML('beforeend', '

' + noResultsText + '

'); + } +} + +function doSearch () { + var query = document.getElementById('mkdocs-search-query').value; + if (query.length > min_search_length) { + if (!window.Worker) { + displayResults(search(query)); + } else { + searchWorker.postMessage({query: query}); + } + } else { + // Clear results for short queries + displayResults([]); + } +} + +function initSearch () { + var search_input = document.getElementById('mkdocs-search-query'); + if (search_input) { + search_input.addEventListener("keyup", doSearch); + } + var term = getSearchTermFromLocation(); + if (term) { + search_input.value = term; + doSearch(); + } +} + +function onWorkerMessage (e) { + if (e.data.allowSearch) { + initSearch(); + } else if (e.data.results) { + var results = e.data.results; + displayResults(results); + } else if (e.data.config) { + min_search_length = e.data.config.min_search_length-1; + } +} + +if (!window.Worker) { + console.log('Web Worker API not supported'); + // load index in main thread + $.getScript(joinUrl(base_url, "search/worker.js")).done(function () { + console.log('Loaded worker'); + init(); + window.postMessage = function (msg) { + onWorkerMessage({data: msg}); + }; + }).fail(function (jqxhr, settings, exception) { + console.error('Could not load worker.js'); + }); +} else { + // Wrap search in a web worker + var searchWorker = new Worker(joinUrl(base_url, "search/worker.js")); + searchWorker.postMessage({init: true}); + searchWorker.onmessage = onWorkerMessage; +} diff --git a/search/search_index.json b/search/search_index.json new file mode 100644 index 0000000..9345655 --- /dev/null +++ b/search/search_index.json @@ -0,0 +1 @@ +{"config":{"indexing":"full","lang":["en"],"min_search_length":3,"prebuild_index":false,"separator":"[\\s\\-]+"},"docs":[{"location":"","text":"What is FNA? FNA is a reimplementation of the Microsoft XNA Game Studio 4.0 Refresh libraries. FNA is primarily developed by video game porter Ethan Lee , who has shipped more than a dozen ports of XNA games using the exact branch that you see on GitHub today! General information about FNA can be found on the FNA website . Those looking for the original XNA 4.0 Refresh documentation can find it here . How do I use FNA? This wiki is designed to be a step-by-step guide to getting your game running on FNA, whether it's an existing XNA game or a brand new game entirely: The zeroth page is an FAQ, just in case. The first three pages are the essential steps necessary to get any title running on FNA, regardless of any other dependencies your game might have. The second page in particular is split up for existing XNA games as well as new games. The fourth and fifth pages go over common mistakes made by XNA games that obstruct the portability provided by FNA and some solutions that can quickly resolve those mistakes. The fifth page also describes features added to FNA that allow further portability and various enhancements to the user experience. The sixth and seventh pages go over ways that FNA can be optimized and tuned specially for your game, as well as some additional materials related to debugging with FNA. Modders may also find these sections interesting when modifying the FNA binary provided with your game. The final page is a rough guide on how you can contribute to FNA's development. It is strongly suggested that you read the documentation in the order provided, and that you read the documentation in full , as every page is meant to be useful to any developer working with FNA. You might be surprised by what you can really do with this library!","title":"Home"},{"location":"#what-is-fna","text":"FNA is a reimplementation of the Microsoft XNA Game Studio 4.0 Refresh libraries. FNA is primarily developed by video game porter Ethan Lee , who has shipped more than a dozen ports of XNA games using the exact branch that you see on GitHub today! General information about FNA can be found on the FNA website . Those looking for the original XNA 4.0 Refresh documentation can find it here .","title":"What is FNA?"},{"location":"#how-do-i-use-fna","text":"This wiki is designed to be a step-by-step guide to getting your game running on FNA, whether it's an existing XNA game or a brand new game entirely: The zeroth page is an FAQ, just in case. The first three pages are the essential steps necessary to get any title running on FNA, regardless of any other dependencies your game might have. The second page in particular is split up for existing XNA games as well as new games. The fourth and fifth pages go over common mistakes made by XNA games that obstruct the portability provided by FNA and some solutions that can quickly resolve those mistakes. The fifth page also describes features added to FNA that allow further portability and various enhancements to the user experience. The sixth and seventh pages go over ways that FNA can be optimized and tuned specially for your game, as well as some additional materials related to debugging with FNA. Modders may also find these sections interesting when modifying the FNA binary provided with your game. The final page is a rough guide on how you can contribute to FNA's development. It is strongly suggested that you read the documentation in the order provided, and that you read the documentation in full , as every page is meant to be useful to any developer working with FNA. You might be surprised by what you can really do with this library!","title":"How do I use FNA?"},{"location":"0%3A-FAQ/","text":"0: FAQ How can I financially contribute to FNA's development? Currently the only way is through GitHub Sponsors . 100% of your contribution goes directly to the lead maintainer (unlike competing crowdfunding services) and your payment information is securely stored and not shared with the FNA team in any way (i.e. \"you don't have to give Some Random Guy your credit card information\"). What is the difference between FNA and MonoGame? Please read the documentation regarding compatibility between the two . For any deeper comparison, the only way to know the difference is to run both of them yourself. I'm new to programming and- Hi there! Glad you've taken an interest in programming. Please do not use FNA yet. While FNA is a programming tool, it is targeted specifically at experienced programmers. If you are not familiar with C# and msbuild (via Visual Studio, MonoDevelop, or maybe just the XML format?) you will not have a good experience. It is required that you learn at least the basics of the above, and also the difference between managed and native code, before using FNA. I was following a tutorial from Website X and- Unless it came directly from this wiki, do NOT use any third party documentation! Most tutorials are very poorly written and completely unmaintained; if there was a good tutorial we would have endorsed it on the front page of our wiki. It's rare to see a tutorial that even gets the development environment right (see below). Our documentation is minimal, but straightforward for experienced C# programmers (see above). What development environments are supported? The only officially supported environments are the following: The environment described on Page 1 MonoDevelop on Fedora Workstation using the mono-project.com repository, targeting .NET Framework with FNA.csproj Visual Studio 2010 or newer on Windows, targeting .NET Framework with FNA.csproj Visual Studio 2019 or newer on Windows, targeting consoles with NativeAOT and FNA.Core.csproj Visual Studio for Mac, targeting iOS/tvOS with FNA.Core.csproj All other environments and runtimes are unsupported and should not be used. Where is the NuGet package? FNA and its sublibraries do not use NuGet in any capacity. We strongly recommend avoiding NuGet in general, and for FNA we recommend adding FNA.csproj (or FNA.Core.csproj) directly to your solution. FNA itself compiles almost instantly, and debug builds are incredibly valuable to have during development. Any and all NuGet packages for any of our code (FNA, SDL2#, etc.) are unauthorized and should be avoided. If the package claims that we're the authors, please report the package as it is misrepresenting its authors and potentially violating the copyright license (i.e. \"... must not be misrepresented as being the original software\"). What is the FNA content system? FNA supports the XNA content pipeline for preservation reasons, but we strongly recommend against using it on new projects. FNA supports loading common data formats like PNG, WAV and OGG directly, and the community maintains a few libraries for font loading and rendering. For anything more specialized you can bring in an external library or write your own processing and importing tools. Your content system does not have to be complex and there is nothing wrong with simple approaches like loading textures from PNG files. How do I use shaders with FNA? FNA uses Direct3D 9 Effects, to match XNA's shader content format. Using fxc.exe, ideally from the DirectX SDK: fxc.exe /T fx_2_0 MyEffect.fx /Fo MyEffect.fxb Other shader formats, languages, etc are not supported. While FXC is a Windows binary, the native d3dcompiler used to build these binaries is known to work with Wine. Where is PlayStation support? If a project comes along and there's money behind it, we'll do it. There are a couple pending projects already, but you should not hesitate to get in touch if you have SDK access and are willing to develop and/or fund the completion of FNA PS4/PS5. Where is Android support? Android is not and cannot be supported. The solution you just thought of does not work. No, not that other one either. Or even the one someone got booting, almost . Internally, we have what's called a \"body count\" for anyone that tries to add support. It was pretty funny, at least for the first dozen increments. We intend to look at Fuchsia once production devices are available. In the meantime, FNA is known to work on PinePhone with various Free mobile operating systems. I get a BadImageFormatException, and- You mixed up 32- and 64-bit binaries. As it turns out, Microsoft broke their CPU arch configuration in two ways: They added a flag to AnyCPU to make it default to 32-bit even on 64-bit Windows. It's a checkbox in your project settings, uncheck it. They made it so new projects don't actually have x86 and x64 configs, only AnyCPU, so you may have added FNA to your solution only to get an x64 entry in your platform dropdown that isn't actually x64! Feel free to keep using AnyCPU and ignore x86/x64 once you've unchecked Prefer 32-bit, but if it's any consolation you can probably just make an x64 config for your project and always use that, so there's no ambiguity as to what arch you're targeting. (Also, once you've done all this, delete bin/ and obj/. No, we don't know why you need to do this.) I have a bug when running on VirtualBox, and- The bug is that you are using VirtualBox. Please use VMware Player instead. I have a bug when running on system Mono, and- Your LD_LIBRARY_PATH is busted. You can do one of three things: Preserve the lib64 folder (like you're supposed to anyway) and set LD_LIBRARY_PATH to include that folder Delete your output's copy of libSDL2-2.0.so.0, keeping the rest of the libs next to your exe, and be sure your distribution provides the latest stable SDL release (maybe don't do this one since it's not the official build) Throw the fnalibs into /usr/local/lib (definitely don't do this one) For shipping builds, use MonoKickstart , do NOT use system dependencies! Where can I get fnalibs for CPU architecture X? If it is not included in the standard fnalibs.tar.bz2 you will need to build the libraries from source. The instructions for each library can be found in their respective README files. What happens if I ask a question that's answered in this wiki? * Bubs voice * That'll be five dollars . No, this isn't a joke. Expect a link to the sponsors page for each of your requests.","title":"0: FAQ"},{"location":"0%3A-FAQ/#0-faq","text":"","title":"0: FAQ"},{"location":"0%3A-FAQ/#how-can-i-financially-contribute-to-fnas-development","text":"Currently the only way is through GitHub Sponsors . 100% of your contribution goes directly to the lead maintainer (unlike competing crowdfunding services) and your payment information is securely stored and not shared with the FNA team in any way (i.e. \"you don't have to give Some Random Guy your credit card information\").","title":"How can I financially contribute to FNA's development?"},{"location":"0%3A-FAQ/#what-is-the-difference-between-fna-and-monogame","text":"Please read the documentation regarding compatibility between the two . For any deeper comparison, the only way to know the difference is to run both of them yourself.","title":"What is the difference between FNA and MonoGame?"},{"location":"0%3A-FAQ/#im-new-to-programming-and-","text":"Hi there! Glad you've taken an interest in programming. Please do not use FNA yet. While FNA is a programming tool, it is targeted specifically at experienced programmers. If you are not familiar with C# and msbuild (via Visual Studio, MonoDevelop, or maybe just the XML format?) you will not have a good experience. It is required that you learn at least the basics of the above, and also the difference between managed and native code, before using FNA.","title":"I'm new to programming and-"},{"location":"0%3A-FAQ/#i-was-following-a-tutorial-from-website-x-and-","text":"Unless it came directly from this wiki, do NOT use any third party documentation! Most tutorials are very poorly written and completely unmaintained; if there was a good tutorial we would have endorsed it on the front page of our wiki. It's rare to see a tutorial that even gets the development environment right (see below). Our documentation is minimal, but straightforward for experienced C# programmers (see above).","title":"I was following a tutorial from Website X and-"},{"location":"0%3A-FAQ/#what-development-environments-are-supported","text":"The only officially supported environments are the following: The environment described on Page 1 MonoDevelop on Fedora Workstation using the mono-project.com repository, targeting .NET Framework with FNA.csproj Visual Studio 2010 or newer on Windows, targeting .NET Framework with FNA.csproj Visual Studio 2019 or newer on Windows, targeting consoles with NativeAOT and FNA.Core.csproj Visual Studio for Mac, targeting iOS/tvOS with FNA.Core.csproj All other environments and runtimes are unsupported and should not be used.","title":"What development environments are supported?"},{"location":"0%3A-FAQ/#where-is-the-nuget-package","text":"FNA and its sublibraries do not use NuGet in any capacity. We strongly recommend avoiding NuGet in general, and for FNA we recommend adding FNA.csproj (or FNA.Core.csproj) directly to your solution. FNA itself compiles almost instantly, and debug builds are incredibly valuable to have during development. Any and all NuGet packages for any of our code (FNA, SDL2#, etc.) are unauthorized and should be avoided. If the package claims that we're the authors, please report the package as it is misrepresenting its authors and potentially violating the copyright license (i.e. \"... must not be misrepresented as being the original software\").","title":"Where is the NuGet package?"},{"location":"0%3A-FAQ/#what-is-the-fna-content-system","text":"FNA supports the XNA content pipeline for preservation reasons, but we strongly recommend against using it on new projects. FNA supports loading common data formats like PNG, WAV and OGG directly, and the community maintains a few libraries for font loading and rendering. For anything more specialized you can bring in an external library or write your own processing and importing tools. Your content system does not have to be complex and there is nothing wrong with simple approaches like loading textures from PNG files.","title":"What is the FNA content system?"},{"location":"0%3A-FAQ/#how-do-i-use-shaders-with-fna","text":"FNA uses Direct3D 9 Effects, to match XNA's shader content format. Using fxc.exe, ideally from the DirectX SDK: fxc.exe /T fx_2_0 MyEffect.fx /Fo MyEffect.fxb Other shader formats, languages, etc are not supported. While FXC is a Windows binary, the native d3dcompiler used to build these binaries is known to work with Wine.","title":"How do I use shaders with FNA?"},{"location":"0%3A-FAQ/#where-is-playstation-support","text":"If a project comes along and there's money behind it, we'll do it. There are a couple pending projects already, but you should not hesitate to get in touch if you have SDK access and are willing to develop and/or fund the completion of FNA PS4/PS5.","title":"Where is PlayStation support?"},{"location":"0%3A-FAQ/#where-is-android-support","text":"Android is not and cannot be supported. The solution you just thought of does not work. No, not that other one either. Or even the one someone got booting, almost . Internally, we have what's called a \"body count\" for anyone that tries to add support. It was pretty funny, at least for the first dozen increments. We intend to look at Fuchsia once production devices are available. In the meantime, FNA is known to work on PinePhone with various Free mobile operating systems.","title":"Where is Android support?"},{"location":"0%3A-FAQ/#i-get-a-badimageformatexception-and-","text":"You mixed up 32- and 64-bit binaries. As it turns out, Microsoft broke their CPU arch configuration in two ways: They added a flag to AnyCPU to make it default to 32-bit even on 64-bit Windows. It's a checkbox in your project settings, uncheck it. They made it so new projects don't actually have x86 and x64 configs, only AnyCPU, so you may have added FNA to your solution only to get an x64 entry in your platform dropdown that isn't actually x64! Feel free to keep using AnyCPU and ignore x86/x64 once you've unchecked Prefer 32-bit, but if it's any consolation you can probably just make an x64 config for your project and always use that, so there's no ambiguity as to what arch you're targeting. (Also, once you've done all this, delete bin/ and obj/. No, we don't know why you need to do this.)","title":"I get a BadImageFormatException, and-"},{"location":"0%3A-FAQ/#i-have-a-bug-when-running-on-virtualbox-and-","text":"The bug is that you are using VirtualBox. Please use VMware Player instead.","title":"I have a bug when running on VirtualBox, and-"},{"location":"0%3A-FAQ/#i-have-a-bug-when-running-on-system-mono-and-","text":"Your LD_LIBRARY_PATH is busted. You can do one of three things: Preserve the lib64 folder (like you're supposed to anyway) and set LD_LIBRARY_PATH to include that folder Delete your output's copy of libSDL2-2.0.so.0, keeping the rest of the libs next to your exe, and be sure your distribution provides the latest stable SDL release (maybe don't do this one since it's not the official build) Throw the fnalibs into /usr/local/lib (definitely don't do this one) For shipping builds, use MonoKickstart , do NOT use system dependencies!","title":"I have a bug when running on system Mono, and-"},{"location":"0%3A-FAQ/#where-can-i-get-fnalibs-for-cpu-architecture-x","text":"If it is not included in the standard fnalibs.tar.bz2 you will need to build the libraries from source. The instructions for each library can be found in their respective README files.","title":"Where can I get fnalibs for CPU architecture X?"},{"location":"0%3A-FAQ/#what-happens-if-i-ask-a-question-thats-answered-in-this-wiki","text":"* Bubs voice * That'll be five dollars . No, this isn't a joke. Expect a link to the sponsors page for each of your requests.","title":"What happens if I ask a question that's answered in this wiki?"},{"location":"1%3A-Setting-Up-FNA/","text":"1: Setting Up FNA This page has two parts: The first sets up the FNA team's recommended development environment, and the second prepares FNA itself. If you already have a development environment you like, you can skip to the Download FNA section if you want, but note that our environment prepares you for remote debugging on Steam Deck . Chapter 1a: Linux Setup The Linux development environment for FNA is supported on all distributions with Flatpak support, including SteamOS! (But, if you absolutely must know, the FNA team uses Fedora Workstation .) You may be able to find VSCode and the .NET SDKs via apps like KDE Discover, but it's easier to get everything at once with a single portable terminal command: flatpak install com.visualstudio.code org.freedesktop.Sdk.Extension.mono6 org.freedesktop.Sdk.Extension.dotnet8 This installs VSCode, Mono, and .NET 8 all at once! If it asks which version of the SDKs to install, select 24.08. All that's left is to expose the .NET and Mono SDKs to VSCode's sandbox: flatpak --user override --env=FLATPAK_ENABLE_SDK_EXT=mono6,dotnet8 com.visualstudio.code Chapter 1b: Windows Setup At minimum you will need to install the following software: Visual Studio Code .NET 8 SDK .NET 4.6.2 Developer Pack Mono for 64-bit Windows When setting up projects, Windows has an additional setup step. An important part of multiplatform development is filesystem case sensitivity - for example, on Linux and console platforms, \"filename\" is NOT the same as \"FileName\"! To ensure that developers follow this rule, it is recommended to make your project folders case sensitive on Windows. To do this, open a Command Prompt as Administrator, then (carefully!) enter the following command: fsutil.exe file SetCaseSensitiveInfo \"C:\\path\\to\\your\\project\" enable (While we're on the subject, remember not to use \\\\ for file paths !) Chapter 2: Visual Studio Code Extensions After starting VSCode, hit Ctrl+Shift+X to go to the extensions view, then search for \"C# Dev Kit\". Install the kit from the marketplace and let it download all of the components. You should be able to clear the search results and see the various new installed extensions! Lastly, search for and install the \"Mono Debug\" extension. Chapter 3: Download and Update FNA We strongly recommend using Git to download and update FNA. This tutorial will guide you through this process. If you are using an official zipped release of FNA, you only need to worry about step 2. Step 1: Clone FNA FNA uses several Git submodules to access the source to additional libraries, such as SDL2# and FAudio. To fully download FNA, add the --recursive parameter to your git clone command: git clone --recursive https://github.com/FNA-XNA/FNA This will clone FNA, then clone all of the submodules into the appropriate locations. Step 2: Download Native Libraries FNA uses several native libraries for various pieces of functionality, such as window management, input, and audio output. Here's what we use and why: SDL2: Used for window management, input, image I/O, etc. FNA3D: Only required if you use the Graphics namespace. FAudio: Only required if you use the Audio or Media namespaces. Theorafile: Only required if you use VideoPlayer. Currently, you can find the libraries precompiled here: https://fna.flibitijibibo.com/archive/fnalibs.tar.bz2 This archive contains all of the native libraries for Windows, Linux, and x86_64 macOS. For arm64 macOS, iOS and tvOS, it is recommended to build libraries using these third-party build scripts . Step 3: Update FNA It is strongly recommended that you update at least once a month. FNA releases are always on the first of every month, so you may simply want to make a calendar reminder for yourself to redownload FNA and fnalibs.tar.bz2 at the beginning of each month. To update FNA, simply enter the FNA directory and run git pull . This will update to the latest FNA version, assuming you have not made local changes that conflict with the upstream changes. If you do have local changes, store them elsewhere and update, or revert your changes. (By the way, if you really do have local changes, please let us know! We want working code in upstream, and it will make your life easier, we promise!) Sometimes, FNA will update one of its submodules. When this occurs, run git submodule update --init --recursive and the submodules will fully update. Again, this assumes that you have not made local changes to the submodules. Step 4: Join the FNA Discord Aside from the commit log, a good place to keep an eye on major FNA changes is to join the Discord server . This is where announcements and development discussion occur; in particular, there are channels allocated for general development, XNA preservation research, private console development, and experimental platform development. Chapter 4: Building Old Visual Studio Projects For those using old pre-.NET Core solutions, you will want to make these changes to allow building your solution: Right click the C# Dev Kit extension and disable it, leaving all other extensions alone Right click the C# extension and select Extension Settings Search for useModernDotNet, uncheck Use Modern .NET Search for useOmnisharp, check Use Omnisharp Create a .vscode folder next to your solution, then add a tasks.json file containing something like this: { // See https://go.microsoft.com/fwlink/?LinkId=733558 // for the documentation about the tasks.json format \"version\": \"2.0.0\", \"tasks\": [ { \"label\": \"build\", \"type\": \"shell\", \"command\": \"msbuild\", \"args\": [ // Ask msbuild to generate full paths for file names. \"/property:GenerateFullPaths=true\", \"/t:build\", // Do not generate summary otherwise it leads to duplicate errors in Problems panel \"/consoleloggerparameters:NoSummary\", \"THIS_IS_YOUR_GAME.sln\", \"/p:Configuration=Debug\" ], \"group\": \"build\", \"presentation\": { // Reveal the output only if unrecognized errors occur. \"reveal\": \"silent\" }, // Use the standard MS compiler pattern to detect errors, warnings and infos \"problemMatcher\": \"$msCompile\" } ] } With this in place, you should now be able to build ( Terminal -> Run Build Task )! Chapter 5: Creating New Projects Making an FNA project is relatively simple for basically every C# IDE, though the process has changed in recent years: For Visual Studio Code, you can use the built-in terminal to navigate to your project folder, then run dotnet new sln --name YourProjectName to create a new solution, dotnet new console --name YourProjectName to create an empty project, and dotnet sln add YourProjectName/YourProjectName.csproj to add it to the solution. Visual Studio and MonoDevelop have New Solution wizards for creating empty C# projects. Once the empty project is created, you can go to the Solution Explorer and right click the solution, click \"Add Existing Project\", then add FNA (FNA.NetFramework.csproj for .NET Framework and Mono, FNA.Core.csproj for .NET 8, or FNA.csproj for old Visual Studio projects). You can then right click the empty project and add FNA as a Project Reference. For existing XNA projects, we recommend targeting .NET Framework and Mono for better compatibility. For new projects, we recommend .NET 8. .NET Framework Fixes When targeting .NET Framework via Visual Studio Code, be sure to open YourProjectName/YourProjectName.csproj and change the target framework to net4.0 ! You'll also find that running/debugging takes a few more steps - we're lobbying to streamline this process, but if you want to get debugging ASAP, you can open the folder where your .sln file is, make a .vscode/ folder, then rip off this launch.json file and place it in the .vscode/ folder: { // Use IntelliSense to learn about possible attributes. // Hover to view descriptions of existing attributes. // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 \"version\": \"0.2.0\", \"configurations\": [ { \"name\": \"Launch\", \"type\": \"mono\", \"request\": \"launch\", \"program\": \"${workspaceRoot}/path/to/your/bin/Debug/net4.0/Game.exe\", \"cwd\": \"${workspaceRoot}\", \"env\": { \"LD_LIBRARY_PATH\": \"${workspaceRoot}/path/to/your/fnalibs/lib64/\" } }, { \"name\": \"Attach\", \"type\": \"mono\", \"request\": \"attach\", \"address\": \"localhost\", \"port\": 55555 } ] } With this in place, you should now be able to launch the program after it's built ( Run -> Start Debugging , or F5!)! .NET Core Fixes DllMap is a critical component of .NET portability that maps native library names to appropriate equivalents on multiple platforms. For example, while Windows might look for fmod_studio.dll , Linux will instead look for libfmodstudio.so.XX , which is nontrivial for the runtime to figure out on its own. By adding a config file like this one , we're able to automatically map DLL names for all platforms without resorting to per-platform builds with customized DLL names. Shamefully, this is currently absent from .NET Core. We will continue to lobby for adding this feature back to modern .NET, but for now we have developed a workaround called FNADllMap . We strongly encourage everyone to copy this file directly and add it to every single project, particularly those that use DllImport, so that all managed EXE/DLL files have a rough equivalent of real DllMap support. Visual Studio AnyCPU Fix For Visual Studio 2019 users, follow these additional steps to allow VS to build your project properly for 64-bit: In the Visual Studio toolbar, click on the Solution Platforms dropdown menu (where it says 'Any CPU'), and click on 'Configuration Manager...' In the Configuration Manager window that appears, change the 'Active solution platform' to x64. Notice that the referenced FNA project changes to x64, but your project remains 'Any CPU' Address this discrepancy by clicking on the platform dropdown for your project and clicking New In the New Project Platform dialog that appears, create a new platform using x64 from the dropdown. Copying settings from Any CPU is fine, and make sure the 'Create new solution platforms' is unchecked You can now build your project for x64!","title":"1: Setting Up FNA"},{"location":"1%3A-Setting-Up-FNA/#1-setting-up-fna","text":"This page has two parts: The first sets up the FNA team's recommended development environment, and the second prepares FNA itself. If you already have a development environment you like, you can skip to the Download FNA section if you want, but note that our environment prepares you for remote debugging on Steam Deck .","title":"1: Setting Up FNA"},{"location":"1%3A-Setting-Up-FNA/#chapter-1a-linux-setup","text":"The Linux development environment for FNA is supported on all distributions with Flatpak support, including SteamOS! (But, if you absolutely must know, the FNA team uses Fedora Workstation .) You may be able to find VSCode and the .NET SDKs via apps like KDE Discover, but it's easier to get everything at once with a single portable terminal command: flatpak install com.visualstudio.code org.freedesktop.Sdk.Extension.mono6 org.freedesktop.Sdk.Extension.dotnet8 This installs VSCode, Mono, and .NET 8 all at once! If it asks which version of the SDKs to install, select 24.08. All that's left is to expose the .NET and Mono SDKs to VSCode's sandbox: flatpak --user override --env=FLATPAK_ENABLE_SDK_EXT=mono6,dotnet8 com.visualstudio.code","title":"Chapter 1a: Linux Setup"},{"location":"1%3A-Setting-Up-FNA/#chapter-1b-windows-setup","text":"At minimum you will need to install the following software: Visual Studio Code .NET 8 SDK .NET 4.6.2 Developer Pack Mono for 64-bit Windows When setting up projects, Windows has an additional setup step. An important part of multiplatform development is filesystem case sensitivity - for example, on Linux and console platforms, \"filename\" is NOT the same as \"FileName\"! To ensure that developers follow this rule, it is recommended to make your project folders case sensitive on Windows. To do this, open a Command Prompt as Administrator, then (carefully!) enter the following command: fsutil.exe file SetCaseSensitiveInfo \"C:\\path\\to\\your\\project\" enable (While we're on the subject, remember not to use \\\\ for file paths !)","title":"Chapter 1b: Windows Setup"},{"location":"1%3A-Setting-Up-FNA/#chapter-2-visual-studio-code-extensions","text":"After starting VSCode, hit Ctrl+Shift+X to go to the extensions view, then search for \"C# Dev Kit\". Install the kit from the marketplace and let it download all of the components. You should be able to clear the search results and see the various new installed extensions! Lastly, search for and install the \"Mono Debug\" extension.","title":"Chapter 2: Visual Studio Code Extensions"},{"location":"1%3A-Setting-Up-FNA/#chapter-3-download-and-update-fna","text":"We strongly recommend using Git to download and update FNA. This tutorial will guide you through this process. If you are using an official zipped release of FNA, you only need to worry about step 2.","title":"Chapter 3: Download and Update FNA"},{"location":"1%3A-Setting-Up-FNA/#step-1-clone-fna","text":"FNA uses several Git submodules to access the source to additional libraries, such as SDL2# and FAudio. To fully download FNA, add the --recursive parameter to your git clone command: git clone --recursive https://github.com/FNA-XNA/FNA This will clone FNA, then clone all of the submodules into the appropriate locations.","title":"Step 1: Clone FNA"},{"location":"1%3A-Setting-Up-FNA/#step-2-download-native-libraries","text":"FNA uses several native libraries for various pieces of functionality, such as window management, input, and audio output. Here's what we use and why: SDL2: Used for window management, input, image I/O, etc. FNA3D: Only required if you use the Graphics namespace. FAudio: Only required if you use the Audio or Media namespaces. Theorafile: Only required if you use VideoPlayer. Currently, you can find the libraries precompiled here: https://fna.flibitijibibo.com/archive/fnalibs.tar.bz2 This archive contains all of the native libraries for Windows, Linux, and x86_64 macOS. For arm64 macOS, iOS and tvOS, it is recommended to build libraries using these third-party build scripts .","title":"Step 2: Download Native Libraries"},{"location":"1%3A-Setting-Up-FNA/#step-3-update-fna","text":"It is strongly recommended that you update at least once a month. FNA releases are always on the first of every month, so you may simply want to make a calendar reminder for yourself to redownload FNA and fnalibs.tar.bz2 at the beginning of each month. To update FNA, simply enter the FNA directory and run git pull . This will update to the latest FNA version, assuming you have not made local changes that conflict with the upstream changes. If you do have local changes, store them elsewhere and update, or revert your changes. (By the way, if you really do have local changes, please let us know! We want working code in upstream, and it will make your life easier, we promise!) Sometimes, FNA will update one of its submodules. When this occurs, run git submodule update --init --recursive and the submodules will fully update. Again, this assumes that you have not made local changes to the submodules.","title":"Step 3: Update FNA"},{"location":"1%3A-Setting-Up-FNA/#step-4-join-the-fna-discord","text":"Aside from the commit log, a good place to keep an eye on major FNA changes is to join the Discord server . This is where announcements and development discussion occur; in particular, there are channels allocated for general development, XNA preservation research, private console development, and experimental platform development.","title":"Step 4: Join the FNA Discord"},{"location":"1%3A-Setting-Up-FNA/#chapter-4-building-old-visual-studio-projects","text":"For those using old pre-.NET Core solutions, you will want to make these changes to allow building your solution: Right click the C# Dev Kit extension and disable it, leaving all other extensions alone Right click the C# extension and select Extension Settings Search for useModernDotNet, uncheck Use Modern .NET Search for useOmnisharp, check Use Omnisharp Create a .vscode folder next to your solution, then add a tasks.json file containing something like this: { // See https://go.microsoft.com/fwlink/?LinkId=733558 // for the documentation about the tasks.json format \"version\": \"2.0.0\", \"tasks\": [ { \"label\": \"build\", \"type\": \"shell\", \"command\": \"msbuild\", \"args\": [ // Ask msbuild to generate full paths for file names. \"/property:GenerateFullPaths=true\", \"/t:build\", // Do not generate summary otherwise it leads to duplicate errors in Problems panel \"/consoleloggerparameters:NoSummary\", \"THIS_IS_YOUR_GAME.sln\", \"/p:Configuration=Debug\" ], \"group\": \"build\", \"presentation\": { // Reveal the output only if unrecognized errors occur. \"reveal\": \"silent\" }, // Use the standard MS compiler pattern to detect errors, warnings and infos \"problemMatcher\": \"$msCompile\" } ] } With this in place, you should now be able to build ( Terminal -> Run Build Task )!","title":"Chapter 4: Building Old Visual Studio Projects"},{"location":"1%3A-Setting-Up-FNA/#chapter-5-creating-new-projects","text":"Making an FNA project is relatively simple for basically every C# IDE, though the process has changed in recent years: For Visual Studio Code, you can use the built-in terminal to navigate to your project folder, then run dotnet new sln --name YourProjectName to create a new solution, dotnet new console --name YourProjectName to create an empty project, and dotnet sln add YourProjectName/YourProjectName.csproj to add it to the solution. Visual Studio and MonoDevelop have New Solution wizards for creating empty C# projects. Once the empty project is created, you can go to the Solution Explorer and right click the solution, click \"Add Existing Project\", then add FNA (FNA.NetFramework.csproj for .NET Framework and Mono, FNA.Core.csproj for .NET 8, or FNA.csproj for old Visual Studio projects). You can then right click the empty project and add FNA as a Project Reference. For existing XNA projects, we recommend targeting .NET Framework and Mono for better compatibility. For new projects, we recommend .NET 8.","title":"Chapter 5: Creating New Projects"},{"location":"1%3A-Setting-Up-FNA/#net-framework-fixes","text":"When targeting .NET Framework via Visual Studio Code, be sure to open YourProjectName/YourProjectName.csproj and change the target framework to net4.0 ! You'll also find that running/debugging takes a few more steps - we're lobbying to streamline this process, but if you want to get debugging ASAP, you can open the folder where your .sln file is, make a .vscode/ folder, then rip off this launch.json file and place it in the .vscode/ folder: { // Use IntelliSense to learn about possible attributes. // Hover to view descriptions of existing attributes. // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 \"version\": \"0.2.0\", \"configurations\": [ { \"name\": \"Launch\", \"type\": \"mono\", \"request\": \"launch\", \"program\": \"${workspaceRoot}/path/to/your/bin/Debug/net4.0/Game.exe\", \"cwd\": \"${workspaceRoot}\", \"env\": { \"LD_LIBRARY_PATH\": \"${workspaceRoot}/path/to/your/fnalibs/lib64/\" } }, { \"name\": \"Attach\", \"type\": \"mono\", \"request\": \"attach\", \"address\": \"localhost\", \"port\": 55555 } ] } With this in place, you should now be able to launch the program after it's built ( Run -> Start Debugging , or F5!)!","title":".NET Framework Fixes"},{"location":"1%3A-Setting-Up-FNA/#net-core-fixes","text":"DllMap is a critical component of .NET portability that maps native library names to appropriate equivalents on multiple platforms. For example, while Windows might look for fmod_studio.dll , Linux will instead look for libfmodstudio.so.XX , which is nontrivial for the runtime to figure out on its own. By adding a config file like this one , we're able to automatically map DLL names for all platforms without resorting to per-platform builds with customized DLL names. Shamefully, this is currently absent from .NET Core. We will continue to lobby for adding this feature back to modern .NET, but for now we have developed a workaround called FNADllMap . We strongly encourage everyone to copy this file directly and add it to every single project, particularly those that use DllImport, so that all managed EXE/DLL files have a rough equivalent of real DllMap support.","title":".NET Core Fixes"},{"location":"1%3A-Setting-Up-FNA/#visual-studio-anycpu-fix","text":"For Visual Studio 2019 users, follow these additional steps to allow VS to build your project properly for 64-bit: In the Visual Studio toolbar, click on the Solution Platforms dropdown menu (where it says 'Any CPU'), and click on 'Configuration Manager...' In the Configuration Manager window that appears, change the 'Active solution platform' to x64. Notice that the referenced FNA project changes to x64, but your project remains 'Any CPU' Address this discrepancy by clicking on the platform dropdown for your project and clicking New In the New Project Platform dialog that appears, create a new platform using x64 from the dropdown. Copying settings from Any CPU is fine, and make sure the 'Create new solution platforms' is unchecked You can now build your project for x64!","title":"Visual Studio AnyCPU Fix"},{"location":"2a%3A-Building-XNA-Games-with-FNA/","text":"2a: Building XNA Games with FNA This is a guide to getting your existing XNA game running on FNA. If you are creating a new game, please see this wiki page . Note that this entire guide can be done on Windows - you do not need a Mac or a Linux box to perform anything described on this page. Also note that this is NOT strictly a guide on having your game fully ported to non-XNA platforms! FNA can only make XNA itself portable; whether or not your game is in any way portable is out of our hands. If this is a concern, look at \" FNA and Windows API \". 1. Making the FNA Solution We strongly recommend making a brand new solution for the FNA version. It's possible to fiddle with your XNA solution to link your code against FNA, but it's probably just easier to make YourGame.FNA.sln and keep YourGame.sln as it is. See Page 1 for a quick refresher on how to do this! One thing you will NOT need to recreate is the content projects. When deploying to FNA, your content should already be complete and built. You can keep this in your solution if you like, but you will need to reference the XNA content pipeline here. You should not reference FNA in content projects! (As an aside, if you are creating new content instead of porting existing content, you should not feel pressured to use processing tools like the XNA/MonoGame content pipeline tools! It is perfectly reasonable to develop your own content system that can be designed and optimized to work well with you and your development team, and in fact that is what we recommend doing for new projects.) Once you've remade your solution with all of your game's subprojects, add FNA.csproj into your solution. FNA, despite using many different C# wrappers, is just a single project file. This simplifies project generation and quickly gives you access to, for example, SDL2# if you need it in your game code. Your projects' references are going to be the same as they were in XNA4, except now you will reference FNA instead of the XNA libraries. Once all of the projects have been made and are linking to FNA, your solution should now compile. Simple as that! 2. About Content Support While we do our best to support 100% of the XNA content available, there are a few notable exceptions due to both technical and legal obstacles. 2a. About Effect Support FNA uses MojoShader to parse and rebuild Effect shaders to non-D3D graphics APIs. This allows us to use the original XNB-packed Effects built by XNA and fx_2_0 effect binaries built by FXC , the Effect compiler from the June 2010 DirectX SDK . However, there are exactly three caveats to this: We do not attempt to undo half-pixel offsets applied by the game's shaders. If you don't know what this is, don't worry. If you do know what this is and have applied them in your engine, simply remove them from the FNA version of your game and it should visually match D3D without any problems. Another half-pixel-like issue present is in the VPOS attribute; D3D9 puts the VPOS at integer values while all other graphics APIs put it at half-pixel values (in contrast to the half-pixel offset described above, where it's the exact opposite). If you find that VPOS isn't acting as expected, take the input VPOS value and floor() it before doing anything else in the shader. For maximum compatibility, vertex input layouts must match the vertex shader input parameters exactly . D3D9 and OpenGL would quietly drop input streams that were unused in the vertex shader, but newer graphics APIs like D3D11 and Vulkan are far more strict about this. For example, if your vertex shader expects a TEXCOORD0 input, but your VertexDeclaration does not include a TextureCoordinate with index 0, this is a hard error unless you forcibly use the OpenGL renderer exclusively. 2b. Rebuilding WMA/WMV Files XNA uses Windows Media Player for the Song and the VideoPlayer implementations. Obviously we cannot use this in a multiplatform reimplementation, so currently we do not support WMA/WMV files. Instead, we support Ogg Vorbis and QOA for audio and Ogg Theora for video. 2c. Rebuilding XACT WaveBanks XACT for Windows supports a codec called xWMA for highly-compressed audio streams, rather than the XMA codec typically used on the 360. Like WMA support we do not have support for either of these, but you can still compress your WaveBanks with ADPCM. The file size will likely be larger, but the functionality of your XACT code/content should be exactly the same, and no changes are required if you do not use xWMA or XMA. 3. Window Icons Typically a Windows application will use the embedded .ico image for both the window icon and the icon used in other parts of the OS, such as the taskbar. While this does work on Windows, it actually turns out to be unusable on other platforms, so we instead use a separate bitmap file to set the process icon. For macOS you don't have to worry about this; the icns file that is discussed in the \" Distributing \" section will cover everything there. For Linux (and optionally Windows, if you want a higher-res icon), simply place a bitmap file in the game's root directory. The bitmap's filename is the same string as the window title, minus the characters that are not allowed for filenames. The recommended image size is at least 512x512, as many desktops will use this icon for more than just the 16x16 image next to the window title. 4. Running the FNA Output Once the FNA version has been built, copy over your Content folder and native libraries (which you should have downloaded along with FNA itself) into the output folder. From there, the game should be able to run! This exact output will be what you run on Linux and macOS. The only difference will be the native libraries in addition to FNA.dll.config , which should be in your output folder even if you're on Windows. FNA.dll.config is what remaps the native DLL names to the proper names of the native libraries on non-Windows operating systems. Aside from this, everything else should work - the C# assemblies, the Content, everything. Push this output to a Linux/macOS box and try them out! When using a developer environment on macOS, you will want to add an environment variable that sets DYLD_LIBRARY_PATH=./osx/ , so that the IDE's runtime environment will find the fnalibs binaries.","title":"2a: Building XNA Games with FNA"},{"location":"2a%3A-Building-XNA-Games-with-FNA/#2a-building-xna-games-with-fna","text":"This is a guide to getting your existing XNA game running on FNA. If you are creating a new game, please see this wiki page . Note that this entire guide can be done on Windows - you do not need a Mac or a Linux box to perform anything described on this page. Also note that this is NOT strictly a guide on having your game fully ported to non-XNA platforms! FNA can only make XNA itself portable; whether or not your game is in any way portable is out of our hands. If this is a concern, look at \" FNA and Windows API \".","title":"2a: Building XNA Games with FNA"},{"location":"2a%3A-Building-XNA-Games-with-FNA/#1-making-the-fna-solution","text":"We strongly recommend making a brand new solution for the FNA version. It's possible to fiddle with your XNA solution to link your code against FNA, but it's probably just easier to make YourGame.FNA.sln and keep YourGame.sln as it is. See Page 1 for a quick refresher on how to do this! One thing you will NOT need to recreate is the content projects. When deploying to FNA, your content should already be complete and built. You can keep this in your solution if you like, but you will need to reference the XNA content pipeline here. You should not reference FNA in content projects! (As an aside, if you are creating new content instead of porting existing content, you should not feel pressured to use processing tools like the XNA/MonoGame content pipeline tools! It is perfectly reasonable to develop your own content system that can be designed and optimized to work well with you and your development team, and in fact that is what we recommend doing for new projects.) Once you've remade your solution with all of your game's subprojects, add FNA.csproj into your solution. FNA, despite using many different C# wrappers, is just a single project file. This simplifies project generation and quickly gives you access to, for example, SDL2# if you need it in your game code. Your projects' references are going to be the same as they were in XNA4, except now you will reference FNA instead of the XNA libraries. Once all of the projects have been made and are linking to FNA, your solution should now compile. Simple as that!","title":"1. Making the FNA Solution"},{"location":"2a%3A-Building-XNA-Games-with-FNA/#2-about-content-support","text":"While we do our best to support 100% of the XNA content available, there are a few notable exceptions due to both technical and legal obstacles.","title":"2. About Content Support"},{"location":"2a%3A-Building-XNA-Games-with-FNA/#2a-about-effect-support","text":"FNA uses MojoShader to parse and rebuild Effect shaders to non-D3D graphics APIs. This allows us to use the original XNB-packed Effects built by XNA and fx_2_0 effect binaries built by FXC , the Effect compiler from the June 2010 DirectX SDK . However, there are exactly three caveats to this: We do not attempt to undo half-pixel offsets applied by the game's shaders. If you don't know what this is, don't worry. If you do know what this is and have applied them in your engine, simply remove them from the FNA version of your game and it should visually match D3D without any problems. Another half-pixel-like issue present is in the VPOS attribute; D3D9 puts the VPOS at integer values while all other graphics APIs put it at half-pixel values (in contrast to the half-pixel offset described above, where it's the exact opposite). If you find that VPOS isn't acting as expected, take the input VPOS value and floor() it before doing anything else in the shader. For maximum compatibility, vertex input layouts must match the vertex shader input parameters exactly . D3D9 and OpenGL would quietly drop input streams that were unused in the vertex shader, but newer graphics APIs like D3D11 and Vulkan are far more strict about this. For example, if your vertex shader expects a TEXCOORD0 input, but your VertexDeclaration does not include a TextureCoordinate with index 0, this is a hard error unless you forcibly use the OpenGL renderer exclusively.","title":"2a. About Effect Support"},{"location":"2a%3A-Building-XNA-Games-with-FNA/#2b-rebuilding-wmawmv-files","text":"XNA uses Windows Media Player for the Song and the VideoPlayer implementations. Obviously we cannot use this in a multiplatform reimplementation, so currently we do not support WMA/WMV files. Instead, we support Ogg Vorbis and QOA for audio and Ogg Theora for video.","title":"2b. Rebuilding WMA/WMV Files"},{"location":"2a%3A-Building-XNA-Games-with-FNA/#2c-rebuilding-xact-wavebanks","text":"XACT for Windows supports a codec called xWMA for highly-compressed audio streams, rather than the XMA codec typically used on the 360. Like WMA support we do not have support for either of these, but you can still compress your WaveBanks with ADPCM. The file size will likely be larger, but the functionality of your XACT code/content should be exactly the same, and no changes are required if you do not use xWMA or XMA.","title":"2c. Rebuilding XACT WaveBanks"},{"location":"2a%3A-Building-XNA-Games-with-FNA/#3-window-icons","text":"Typically a Windows application will use the embedded .ico image for both the window icon and the icon used in other parts of the OS, such as the taskbar. While this does work on Windows, it actually turns out to be unusable on other platforms, so we instead use a separate bitmap file to set the process icon. For macOS you don't have to worry about this; the icns file that is discussed in the \" Distributing \" section will cover everything there. For Linux (and optionally Windows, if you want a higher-res icon), simply place a bitmap file in the game's root directory. The bitmap's filename is the same string as the window title, minus the characters that are not allowed for filenames. The recommended image size is at least 512x512, as many desktops will use this icon for more than just the 16x16 image next to the window title.","title":"3. Window Icons"},{"location":"2a%3A-Building-XNA-Games-with-FNA/#4-running-the-fna-output","text":"Once the FNA version has been built, copy over your Content folder and native libraries (which you should have downloaded along with FNA itself) into the output folder. From there, the game should be able to run! This exact output will be what you run on Linux and macOS. The only difference will be the native libraries in addition to FNA.dll.config , which should be in your output folder even if you're on Windows. FNA.dll.config is what remaps the native DLL names to the proper names of the native libraries on non-Windows operating systems. Aside from this, everything else should work - the C# assemblies, the Content, everything. Push this output to a Linux/macOS box and try them out! When using a developer environment on macOS, you will want to add an environment variable that sets DYLD_LIBRARY_PATH=./osx/ , so that the IDE's runtime environment will find the fnalibs binaries.","title":"4. Running the FNA Output"},{"location":"2b%3A-Building-New-Games-with-FNA/","text":"2b: Building New Games with FNA Before You Start This is strictly a tutorial about using FNA. It is NOT a C# tutorial! If you are learning C# for the first time, use Microsoft's official Introduction to C# on MSDN first before continuing on. Also, be sure to finish Page 1 before starting this page! What is XNA? XNA was, at its core, the software equivalent of an 80's Saturday morning cartoon based on a toy line: A massive advertisement masquerading as a real product. It was built to advertise many new (at the time) products in development at Microsoft: C# 2.0 Direct3D Effects Framework XACT Audio Creation Tool XInput and the Xbox 360 Controller Xbox 360 + Windows Media Center From 2006 to 2010, Microsoft maintained XNA as a means of allowing independent game developers to ship small games written in C# on Xbox 360, via the \"Xbox Live Indie Games\" marketplace. The final XNA release also supported building for Windows Phone 7 devices. As for the XNA API, it was largely a C# wrapper for various DirectX components, but not quite all of them - many features are unavailable in favor of the aforementioned new-fangled DirectX products. For example, while there is a GraphicsDevice class that effectively acts as a 1:1 map of ID3D10Device , notably missing is support for low-level shaders and constant buffers; instead you are expected to use Effects for shader support. XNA was officially discontinued in 2012, and the Xbox Live Indie Games marketplace was shut down on November 2017. What is FNA? FNA is a preservation project designed to accurately reimplement the XNA runtime libraries. When you have an XNA game, you should be able to take the source, compile it against FNA, and have a fully-functioning port. At its core, FNA is a portability library, but many continue to develop new games with FNA. This tutorial will help you make your own FNA games, without needing XNA as a prerequisite. If you are bringing an existing XNA game to FNA, follow this wiki page instead. Your First Game See Page 1 for a quick refresher on making new projects. Once you have a project made, you can then proceed: The First Program This is the smallest possible program using the framework portion of XNA: using System; using Microsoft.Xna.Framework; static class Program { [STAThread] static void Main(string[] args) { using (Game g = new Game()) { new GraphicsDeviceManager(g); g.Run(); } } } This should compile into a folder like bin/Debug/ . Next to your executable, you will put the native libraries you downloaded earlier into this folder. You only need to worry about the libraries for your development platform; the rest will be for when you distribute your game . For example, if you're building an AnyCPU program on Windows x64, you would take the contents of the native library archive's x64 folder and put them next to your exe. When using a developer environment on macOS, you will want to add an environment variable that sets DYLD_LIBRARY_PATH=./osx/ (or wherever your dylib files are), so that the IDE's runtime environment will find the fnalibs binaries. When running this program, you might see some random trash in the window; that is most likely old graphics memory from another program you were running. Aside from that, the game is fully functional; it is reading input, running updates, and presenting frames to the window. But if this is the whole program, where do we put the rest of the game? The First Game Object The trick is that you're not going to create a Game directly. Instead, you're going to inherit it! using System; using Microsoft.Xna.Framework; class FNAGame : Game { [STAThread] static void Main(string[] args) { using (FNAGame g = new FNAGame()) { g.Run(); } } private FNAGame() { // This gets assigned to something internally, don't worry... new GraphicsDeviceManager(this); } } But again, there's still no place to put the game. That's because Game has several protected methods that you are meant to implement. Here's what it looks like with the most commonly-used methods: using System; using Microsoft.Xna.Framework; class FNAGame : Game { [STAThread] static void Main(string[] args) { using (FNAGame g = new FNAGame()) { g.Run(); } } private FNAGame() { GraphicsDeviceManager gdm = new GraphicsDeviceManager(this); // Typically you would load a config here... gdm.PreferredBackBufferWidth = 1280; gdm.PreferredBackBufferHeight = 720; gdm.IsFullScreen = false; gdm.SynchronizeWithVerticalRetrace = true; } protected override void Initialize() { /* This is a nice place to start up the engine, after * loading configuration stuff in the constructor */ base.Initialize(); } protected override void LoadContent() { // Load textures, sounds, and so on in here... base.LoadContent(); } protected override void UnloadContent() { // Clean up after yourself! base.UnloadContent(); } protected override void Update(GameTime gameTime) { // Run game logic in here. Do NOT render anything here! base.Update(gameTime); } protected override void Draw(GameTime gameTime) { // Render stuff in here. Do NOT run game logic in here! GraphicsDevice.Clear(Color.CornflowerBlue); base.Draw(gameTime); } } The First Input It's not a game without input, right? FNA exposes GamePad , Keyboard , and Mouse for user input. There is also a Microsoft.Xna.Framework.Input.Touch namespace for touch screen support. Input isn't terribly complicated; you store two copies of input state, one for current input and another for previous input. This lets you detect presses and releases, in addition to just checking for a button being down: using System; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Input; class FNAGame : Game { [STAThread] static void Main(string[] args) { using (FNAGame g = new FNAGame()) { g.Run(); } } private KeyboardState keyboardPrev = new KeyboardState(); private MouseState mousePrev = new MouseState(); private GamePadState gpPrev = new GamePadState(); private FNAGame() { new GraphicsDeviceManager(this); } protected override void Update(GameTime gameTime) { // Poll input KeyboardState keyboardCur = Keyboard.GetState(); MouseState mouseCur = Mouse.GetState(); GamePadState gpCur = GamePad.GetState(PlayerIndex.One); // Check for presses if (keyboardCur.IsKeyDown(Keys.Space) && keyboardPrev.IsKeyUp(Keys.Space)) { System.Console.WriteLine(\"Space bar was pressed!\"); } if (mouseCur.RightButton == ButtonState.Released && mousePrev.RightButton == ButtonState.Pressed) { System.Console.WriteLine(\"Right mouse button was released!\"); } if (gpCur.Buttons.A == ButtonState.Pressed && gpPrev.Buttons.A == ButtonState.Pressed) { System.Console.WriteLine(\"A button is being held!\"); } // Current is now previous! keyboardPrev = keyboardCur; mousePrev = mouseCur; gpPrev = gpCur; base.Update(gameTime); } protected override void Draw(GameTime gameTime) { GraphicsDevice.Clear(Color.CornflowerBlue); base.Draw(gameTime); } } Be sure to read all of the input APIs for more details! You may also be interested in some extensions to the XNA spec that improve input support in FNA. The First Sprite Finally, some graphics! In XNA, there is a class called SpriteBatch that makes sprite drawing relatively easy. Combine that with your own textures and you have the foundation of a 2D renderer. This sample loads a PNG named \"FNATexture\", located in a \"Content\" folder, and renders it with a SpriteBatch: using System; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; class FNAGame : Game { [STAThread] static void Main(string[] args) { using (FNAGame g = new FNAGame()) { g.Run(); } } private SpriteBatch batch; private Texture2D texture; private FNAGame() { new GraphicsDeviceManager(this); // All content loaded will be in a \"Content\" folder Content.RootDirectory = \"Content\"; } protected override void LoadContent() { // Create the batch... batch = new SpriteBatch(GraphicsDevice); // ... then load a texture from ./Content/FNATexture.png texture = Content.Load(\"FNATexture\"); } protected override void UnloadContent() { batch.Dispose(); texture.Dispose(); } protected override void Draw(GameTime gameTime) { GraphicsDevice.Clear(Color.CornflowerBlue); // Draw the texture to the corner of the screen batch.Begin(); batch.Draw(texture, Vector2.Zero, Color.White); batch.End(); base.Draw(gameTime); } } If all went well, the PNG you chose should now be displayed! When drawing sprites, be absolutely sure that you draw as many as you possibly can before calling End() ; batches are meant to be large, singular groups rather than lots of small, fragmented groups. The more you put in a single batch, the better your program will perform. And whatever you do, do NOT use SpriteSortMode.Immediate ! The First Sound In addition to XACT, there is also a SoundEffect API available. It's as simple as loading a .wav file and mashing Play() : using System; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Audio; using Microsoft.Xna.Framework.Input; class FNAGame : Game { [STAThread] static void Main(string[] args) { using (FNAGame g = new FNAGame()) { g.Run(); } } private SoundEffect sound; private KeyboardState keyboardPrev = new KeyboardState(); private FNAGame() { new GraphicsDeviceManager(this); // All content loaded will be in a \"Content\" folder Content.RootDirectory = \"Content\"; } protected override void LoadContent() { // Sound is ./Content/FNASound.wav sound = Content.Load(\"FNASound\"); } protected override void UnloadContent() { sound.Dispose(); } protected override void Update(GameTime gameTime) { KeyboardState keyboardCur = Keyboard.GetState(); if (keyboardCur.IsKeyDown(Keys.Space) && keyboardPrev.IsKeyUp(Keys.Space)) { sound.Play(); } keyboardPrev = keyboardCur; } protected override void Draw(GameTime gameTime) { GraphicsDevice.Clear(Color.CornflowerBlue); base.Draw(gameTime); } } There is lots of deeper functionality, including instance management, 3D audio APIs, and even a streaming sound object, useful for streaming from larger files (for example, sending decoded data from an Ogg Vorbis music file). The First Song XNA includes a Media namespace, which includes support for basic playback of music and video files. FNA supports Ogg Vorbis and QOA for the Song implementation: using System; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Media; class FNAGame : Game { [STAThread] static void Main(string[] args) { using (FNAGame g = new FNAGame()) { g.Run(); } } private Song song; private FNAGame() { new GraphicsDeviceManager(this); // All content loaded will be in a \"Content\" folder Content.RootDirectory = \"Content\"; } protected override void LoadContent() { // Song is ./Content/FNASong.ogg song = Content.Load(\"FNASong\"); } protected override void UnloadContent() { song.Dispose(); } protected override void Update(GameTime gameTime) { // Just keep playing the song over and over if (MediaPlayer.State == MediaState.Stopped) { MediaPlayer.Play(song); } base.Update(gameTime); } protected override void Draw(GameTime gameTime) { GraphicsDevice.Clear(Color.CornflowerBlue); base.Draw(gameTime); } } The First Video Video objects are a fair bit more involved than Song . In addition to playing the sound, a VideoPlayer will provide the frames in the form of a texture, which you can then render however you like: using System; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Media; using Microsoft.Xna.Framework.Graphics; class FNAGame : Game { [STAThread] static void Main(string[] args) { using (FNAGame g = new FNAGame()) { g.Run(); } } private GraphicsDeviceManager gdm; private Video video; private VideoPlayer videoPlayer; private SpriteBatch batch; private FNAGame() { gdm = new GraphicsDeviceManager(this); // All content loaded will be in a \"Content\" folder Content.RootDirectory = \"Content\"; } protected override void LoadContent() { // Video is ./Content/FNAVideo.ogv video = Content.Load