Skip to content

rbrich/xcikit

Repository files navigation

xcikit

1. About

Xcikit contains basic elements needed for creating 2D graphical applications. The focus is on text rendering and closely related UI rendering. Additional features are a custom scripting language and data serialization.

With xcikit you can:

  • render a few paragraphs of text,

  • style some parts of the text differently (colored highlights),

  • respond to mouse hover, click on the highlighted parts (spans),

  • create a basic UI with buttons, checkboxes, combo-boxes,

  • support scripting, provide sandboxed API for user scripts.

This should be enough for a program to render:

  • 2D sprites,

  • menu,

  • settings screen,

  • dialogs.

The library uses GLFW and Vulkan for graphics.

1.1. Name

XCI is 91 in Roman numerals. I perceive 91 as a beautiful number.

It might stand for Extended C++ Interface, but this is an afterthought.

2. Features

Note that xcikit is still under development and mostly experimental. There is no stable API. There are no releases. The features below are partially planned, but already implemented to some degree.

The planned features:

  • GPU oriented 2D graphics

  • advanced text rendering

  • UI widgets (not meant to replace Qt, but should be good enough for indie games)

  • data file management (VFS)

  • custom scripting language (it wasn’t planned originally, but it’s fun to design and implement)

  • support library (logging, eventloop etc.)

The features are divided into a set of libraries:

  • xci-widgets

  • xci-text

  • xci-graphics

  • xci-script

  • xci-data

  • xci-core

The separation of the code into libraries helps to create layers and avoid bi-directional dependencies. Basically, each library can depend only on the libraries listed below it. (TODO: draw a dependency graph)

The top-level xci-widgets provides UI components with event processing. It uses text layout engine xci-text, which can also be used separately.

All rendering goes through GLFW and Vulkan based xci-graphics library. Graphics lib can be used separately when neither UI nor text rendering is needed.

Optional xci-data library provides custom text and binary format for serialization / deserialization of hierarchical data structures. Think of Json and Google Protobuf, but without the complexity and bloat.

All the above libraries use xci-core, which contains miscellaneous utilities, This can also be used separately.

There is also header-only compat library, which is used to hide differences between compilers and OS’s. It does not implement any abstraction layer, but just provides what is available elsewhere.

Technologies:

  • C++20 as main programming language

  • CMake as build system

  • Conan as dependency manager

Supported compilers:

  • GCC >= 10

  • Clang >= 13

  • Xcode >= 13

  • Visual Studio >= 16

Any Unix-like OS with C++20 compliant compiler should work. Windows OS is also supported, to some extent. See Building on Windows.

3. Libraries

3.1. xci::widgets

Basic UI elements.

3.2. xci::text

Text rendering and text layout.

3.3. xci::graphics

The basic building blocks for rendering of text and UI elements.

3.4. xci::scene

Support for building 3D scenes.

3.5. xci::script

Experimental scripting language with bytecode interpreter.

Docs:

# Compile and run a script
fire examples/script/some.fire

# Compile script to bytecode
fire -c examples/script/some.fire --output-schema /tmp/firm.schema
# Inspect binary format with the compiled bytecode
dati examples/script/some.firm -s /tmp/firm.schema
# Execute compiled bytecode
fire examples/script/some.firm

3.6. xci::vfs

Virtual file system. Unified reading of files in filesystem directories and in archive files. Contains support for custom archive format - DAR.

3.7. xci::data

Serialization and deserialization of structured data.

3.8. xci::config

Parse and dump config file. The format is custom:

bool_item false   // true/false
int_item 1
float_item 2.3
string_item "abc\n"  // quotes are required, supports C-style escape sequences
group {
  value 1
  subgroup { foo 42; bar "baz" }  // semicolon is used as a delimiter for multiple items on same line
}

3.9. xci::math

Linear algebra: vectors, matrices, 2D rectangle, geometric computations.

3.10. xci::core

Core utilities. These have little or no dependencies. Mostly just stdlib + OS API.

  • Buffer (types.h) - Owned blob of data, with deleter.

  • FpsCounter - Tracks delays between frames and computes frame rate.

  • Logger (log.h) - Logging functions.

  • SharedLibrary - Thin wrapper around dlopen. For plugins.

  • TermCtl - Colored output for ANSI terminals.

  • Vfs - Unified reading of regular files and archives. Mount the archive to virtual path and read contained files in same fashion as regular files.

  • bit.h - custom bit_copy, bit_read, similar to C++20 bit_cast

  • event.h - System event loop (abstraction of kqueue / epoll).

  • dispatch.h - Watch files and notify on changes. Useful for auto-reloading of resource files.

  • file.h - Read whole files. Path utilities (dirname, basename, …).

  • format.h - Formatted strings. Similar to Python’s format().

  • rtti.h - C++ demangled type names.

  • string.h - String manipulation, unicode utilities.

  • sys.h - A replacement for std::this_thread::get_id(), providing the canonical TID.

3.11. xci::compat

Fill gaps between different systems and compilers.

  • dl.h - dlopen etc. for Windows

  • endian.h - Linux-like macros provided for macOS and Windows

  • macros.h - XCI_UNREACHABLE, XCI_INLINE, XCI_UNUSED

  • unistd.h - Minimal Unix compatibility header for Windows

4. Tools

Included are some tools build on top of the libraries. Check them on separate pages:

5. How to build

TL;DR:

5.1. Dependencies

Tools:

  • CMake - build system

  • Conan - optional package manager

Libraries:

Obtaining dependencies:

  • Install them into system or otherwise make them visible to CMake’s find_* functions.

    • This works for almost all deps, except fmt, pfr, magic_enum.

    • Deps that must be installed this way: libzip, hyperscan.

  • Build deps from Git locally for this project

    • Run ./build_deps.sh, it will clone each repo and build it via CMake to ./.deps

    • The build script picks up these deps as if they were installed in the system

  • Install them via Conan. See How to build from IDE or build with build.sh which runs conan automatically.

    • Conan can be skipped via --no-conan-deps (if you have all deps preinstalled in system or in ./.deps)

Installing the dependencies with system package managers:

  • Debian:

    apt install libzip-dev cmake ninja-build
    pip3 install conan
  • macOS (Homebrew):

    # Tools
    brew install cmake ninja ccache
    pip3 install conan
    # Libs
    brew install pegtl libzip freetype sdl2 doxygen catch2 google-benchmark hyperscan
  • macOS (MacPorts):

    # Tools
    port install cmake ninja ccache
    pip3 install conan
    # Libs
    port install vulkan-* MoltenVK libsdl2 harfbuzz-devel hyperscan libfmt catch2

5.2. Using build script

The complete build process is handled by a build script:

./build.sh

When finished, you’ll find the temporary build files in build/ and installation artifacts in artifacts/.

Both scripts are incremental, so it’s safe to run them repeatably. They do only the required work and re-use what was done previously.

5.3. Manual build with CMake

Detailed build steps (these are examples, adjust parameters as needed):

# Prepare build directory
mkdir build && cd build

# Install dependencies using Conan.
conan install .. --build missing

# Configure
cmake .. -G Ninja -DCMAKE_INSTALL_PREFIX=~/sdk/xcikit

# Run CMake UI to adjust the parameters
ccmake ..

# Build
cmake --build .

# Run unit tests
cmake --build . --target 'test'

# Install artifacts
cmake --build . --target 'install'

5.4. Building on macOS older than Catalina (10.15)

Using MacPorts, install Clang 14 and libc++:

port install clang-14 macports-libcxx

Then create a clang14-toolchain.cmake file with content like this:

set(CMAKE_CXX_COMPILER /opt/local/bin/clang++-mp-14)
add_compile_options(-nostdinc++ -I/opt/local/include/libcxx/v1 -D_LIBCPP_DISABLE_AVAILABILITY)
add_link_options(-L/opt/local/lib/libcxx)

Run the build with the toolchain:

./build.sh --toolchain clang14-toolchain.cmake

5.5. Building on Windows

Almost everything is portable and should work:

  • build scripts (using git-bash)

  • dependencies via Conan

  • build with CMake + ninja + cl.exe

  • all libraries, examples, tests

What doesn’t work:

  • find_file (ff) tool - it’s built on low-level unix APIs, probably unportable

5.5.1. How to build from command line

  1. (Optional) Enable Developer mode to obtain ability to create symlinks

  2. Install Git Bash and run it

  3. We need these commands to work:

    • git (to clone the project)

    • cmake, ninja (build tools)

    • conan 1.x (C++ package manager)

    • cl (VS compiler)

  4. Beginning from the last one:

    • Find vcvars64.bat in Visual Studio installation. I have Visual Studio Build Tools 2019 and it’s here: "C:\Program Files (x86)\Microsoft Visual Studio\2019\BuildTools\VC\Auxiliary\Build\vcvars64.bat" (The directory slightly differs for Community and Professional edition.)

    • Paste the path including the quotes into Git Bash and execute it.

    • Now we should have cl, cmake and ninja working.

    • (For convenience, add Git Bash + VS configuration to Windows Terminal.)

  5. Install conan: pip install conan (Python should also work via VS developer tools)

  6. Run ./build.sh

Note that the build script detects "MINGW64_NT" as target platform, but this is not true. It builds native Windows binaries. The string is just the output of uname in Git Bash. I don’t know any better way how to detect host platform on Windows (please tell me if you know).

5.5.2. How to build from IDE with CMake support

Works with Clion, and may work with Visual Studio (I did not test).

Separately, clone this fork of cmake-conan which adds CONAN_OPTIONS: https://github.com/rbrich/cmake-conan

Then open the xcikit project, let the IDE load the top-level CMakeLists.txt.

Add these CMake options in the IDE settings:

-DCMAKE_PROJECT_TOP_LEVEL_INCLUDES=/path/to/cmake-conan/conan_provider.cmake
-DCONAN_OPTIONS="-o;xcikit/*:system_sdl=True;-o;xcikit/*:system_vulkan=True;-o;xcikit/*:system_freetype=True;-o;xcikit/*:system_harfbuzz=True;-o;xcikit/*:system_zlib=True"

The CONAN_OPTIONS are just an example, in this case Conan will skip installing graphical libs, so they can be searched in the system instead. Those libs must be preinstalled.

5.5.3. Troubleshooting

If linking tests fails with unresolved symbols from catch2 (error LNK2019: unresolved external symbol …​), it’s because Conan picked incompatible package. To avoid issues like this, it’s best to completely disable the compatibility extension.

There are multiple ways how to disable it. One way is to edit cppstd_compat.py and make it always return []. Do not forget to also remove header comments, so Conan doesn’t destroy your changes.

5.6. Porting to Emscripten

The non-graphical components should build with Emscripten.

Install and link Emscripten so that this command works: emcc -v

Create a Conan profile for Emscripten, for example:

[settings]
os=Emscripten
arch=wasm
compiler=clang
compiler.version=14
compiler.libcxx=libc++
compiler.cppstd=20
build_type=MinSizeRel
[options]
[build_requires]
[conf]
# Find actual path Emscripten installation (or check CMake command line, when called with emcmake)
tools.cmake.cmaketoolchain:user_toolchain = [".../cmake/Modules/Platform/Emscripten.cmake"]
[env]
# Same as above, for older packages which do not use tools.cmake.CMakeToolchain
CONAN_CMAKE_TOOLCHAIN_FILE = .../cmake/Modules/Platform/Emscripten.cmake
CXXFLAGS=-flto=thin
LDFLAGS=-flto=thin

See a working example of such profile in the docker/emscripten directory.

Run the build (only 'core' and 'script' components work at this time):

./build.sh --debug --emscripten --profile emscripten core script

5.7. Building Conan package

The defaults in the Conan recipe (conanfile.py) are meant to make it easier for consumers (conan install) than packagers. That means, the tests and examples are built and packaged by default. Add the following options to build a package with only the development libs:

conan create . --build=missing -o xcikit:tools=False -o xcikit:examples=False -o xcikit:tests=False -o xcikit:benchmarks=False

6. How to use in a client program

6.1. Linking with a client using only CMake

Build and install XCI libraries (see "How to build" above), then use installed xcikit-config.cmake in your project’s CMakeLists.txt:

cmake_minimum_required(VERSION 3.16)
project(example CXX)

find_package(xcikit REQUIRED)

add_executable(example src/main.cpp)
target_link_libraries(example xci-widgets)

In the case xcikit was installed into non-standard location, for example ~/sdk/xcikit, you need to set up CMAKE_PREFIX_PATH appropriately:

cmake -DCMAKE_PREFIX_PATH="~/sdk/xcikit" ..

6.2. Linking with a client using CMake and Conan

Add xcikit as dependency to conanfile.txt:

[requires]
xcikit/0.1@rbrich/stable

[generators]
cmake_paths

Then include generated conan_paths.cmake from project’s CMakeLists.txt:

if (EXISTS ${CMAKE_BINARY_DIR}/conan_paths.cmake)
    include(${CMAKE_BINARY_DIR}/conan_paths.cmake)
endif()

Now find xcikit in usual way:

find_package(xcikit CONFIG REQUIRED)

Optionally, include XCI goodies:

include(XciBuildOptions)

Link with the libraries:

target_link_libraries(example xcikit::xci-text xcikit::xci-graphics)