Random Tech Stuff Day @ v01d

We’re planning an event for v01d members to ‘mutually show off’ what we’re working on and possibly to recruit new members. This event will consist of 4 talks/workshops by v01d members concerning whatever they are working on at the moment.

For my part, I’ll be talking about technical details of mechanical keyboards (including two different keyboards for people to try out), people making their own keyboards and keyboard-centric UIs (from Vim and inspired UIs to keyboard-heavy RTS UIs).

Basic outline of the event, as announced on the gamedev meetup a few days ago:

Random Tech Stuff Day

Saturday 20.02.2016, 13:00-17:30 @v01d (Narodna Trieda 74)

  • Ferdinand Majerech: The Keyboard Life

    Mechanical keyboards, the excentrics around them, and keyboard UIs

  • Radoslav Strobl: DIY Desktop Environment

    Building a desktop on Awesome; a Lua programmable Window Manager

  • Eduard Drusa: AVR/embedded ghetto style

    How I built my AVR stack from scratch, and how far it is from blinkenlights to a smart home controller

  • Lubomir Nagajda & Co.: Synthetic speech TBD

    Content depends on research progress

KE gamedev meetup 1

This Thursday we’ve had the first ‘official’ gamedev meetup in v01d, after the ‘zeroth’ experiment last autumn.

My expectations of too few or too many people ended up being unsubstantiated; we had a slightly larger attendance than last time, but we were ‘just full’, without being cramped. Mandatory registration of attendees would add some certainty here, but i’m reluctant to introduce it, as walking in is the most ‘user friendly’ way to attend. We still have a problem there as our substitute for a doorbell is ‘knocking at the window with a Snowden poster’.

We’ve started on time (not without technical problems, but those were solved ahead) with Peter Adamondy’s talk, which introduced Triple Hill‘s recently released game Clumzee, and morphed through Triple Hill’s game design philosophy to a preview of concepts behind their current, as of yet unannounced project.

While the talk was a success, by the time it was over, we ran through most of time expected for the entire meetup. I had to cut off discussion to give space to the next guest/contributor, Martin Baco. Communication with contributors is an area I need to work on overall; I only mentioned time allocation once in months-long conversations with contributors and I didn’t sufficiently communicate some details, e.g. Martin had expected at first to show a completely different project on the assumption that the meetup was not open to public.

Martin’s ‘talk’ was no talk; it was a live human experiment where I took the role of the guinea pig. Fishcow has apparently spent a chunk of their time trying to create the ‘Next Weird Thing’ in the footsteps of such hits as Goat Simulator. It is perhaps unfortunate that the concept I played as a part of the experiment did not come to fruition; it was one of those extremely frustrating games that do not let go, and it’s ‘physics so bad it was good’ might make for some interesting YouTube memes if noticed.

Overall, compared to the zeroth experimental meetup the first was a major success, even though it was still far from perfect and there is still a lot of space for improvement.

Takeaways for the next meetup

  • Explicitly work out the amount of time allocated for every talk/demo/etc. with guests/contributors.
  • Make sure all contributors are aware in detail of the format, e.g. the fact that it’s a public meetup.
  • Non-mandatory registration of interest (e.g. on v01d’s website) could still be helpful, at least to get a lower bound on the number of attendees.
  • Don’t bother that much with making the space look clean beforehand... it won’t look much better either way and there’ll be a lot to clean after, especially now in winter.

Future meetups

For future, especially assuming we get more people, we’re going to need more space than possible in v01d. Even with some creative arrangement (e.g. tabourettes in front of higher chairs) we can probably only accomodate <25 people.

I’m hoping to move the meetup to UPJS, where we should be able to get enough space for around 50 people worst-case. This will likely require some compromises, e.g. earlier starting time as we’d be required to be done by 19:00.

My current plan for the near-future meetups is as follows:

  • 19. March: ~ v01d
  • May: ~ UPJS
  • July: ~ v01d (summer holidays)
  • September: ~ UPJS (if possible; may be early)

Note: the dates at UPJS are only my plan; they’re not secured yet and may move to v01d. Everything may still change.

KE gamedev meetup; take 0

Yesterday, we had the zeroth gamedev meetup in Košice, Slovakia; which was my first attempt at ‘organizing’ such a thing (AKA: sending a bunch of emails and killing a lot of spiders).

A major twist was making this attempt in a hackerspace; Slovakia doesn’t have much of a ‘hacker-gamedev’ culture at this point.

The presence of both game developers and hackers in the same room has lead to a bunch of awkward moments as well as unintended comedy. Overall I think this combination could be interesting if maintained in future. For one, I’ve noticed that hackers and gamedevs can have a sustained conversation on two separate topics while staying under the impression of discussing the same topic; This tends to eventually fall appart as the inconsistency eventually becomes too evident.

The event had a bunch of unexpected issues that should be avoided in future:

  • We’ve noticed a bit too late that v01d has no... ‘coat hanging devices’ on the brink of winter. Our awesome 4D chair has served as a temporary substitute, though not a particularly ergonomic one.
  • Randomly buying a bunch of low-quality food resulted in a bunch of low-quality food being left unused. Need to decrease low-quality food consumption estimates.

A more major (and somewhat expected) issue was the lack of capacity of the new v01d space; while an upgrade compared to the previous space, it was filled to capacity by 10-15 people. Fully equipping v01d (new chairs, some rearrangement) will slightly improve the capacity, where slightly <= 5. Anything more would likely lead to air quality issues and just the plain ‘stuffed in a tin can’ effect.

We will need a larger space for the meetup eventually, where eventually is ASAP. Universities are an option that would likely work, though may lead to some bureaucracy. I will keep looking for alternatives.

Not much has been done on the meetup (no talks, demos, etc. as there was no pre-planned content); two main results have been:

  • Creation of the v01d gamedev FB group. This will reduce my direct participation as I also count among the more staunch FB non-users; this might be for the better as my intention is to act as a ‘placeholder’ only for whatever necessities are not covered by other participants; eventually I’d like to see these events be either mostly self-organized or professionally organized (the latter may be harder at the moment due to the size of our community).
  • Tentative date of the next meetup: 07.01.2015. This may or may not change, though.

Making graphics for games

Apparently, this blog has been dead for a while.

This was caused mainly by my work on my thesis project, which is now complete as far as the thesis is concerned (though the project still needs some work).

Right now, however, most of my free time is invested in working on what is supposed to be an introductional course/subject into game development for UPJS (still not set in stone), consisting of a number of high-level independent talks/workshops that can be given in any order.

To ensure the talks are at least a bit non-crap I’m going to ‘test’ some of them in local events. The first such test has occured a week ago in the v01d hackerspace.

As usual, the slides are both presentation slides and tutorial material to be viewed by the student.

Intro to gamedev using D

After ascertaining that I’m not going to spend any more time on workshops or talks, I ended up preparing a... workshop for a local OSS weekend event.

The workshop is an introduction to game development in the D language, with a focus on making a game (for people who never made a game) rather than trying to show off all the features of D.

Slides I prepared for the worshop are not really presentation slides, but rather a thinly veiled tutorial with code to copy and bullet-points instead of long-winded paragraphs. We’ll see if that makes any sense at all.

Profiling (on Linux)

Recently I gave a talk about profiling and optimization at a local hackerspace. The talk is mostly oriented at C/C++ but is also useful for D and other native languages. It gives a basic overview of various useful (not gprof) profiling tools on Linux as well as some optimizations possible with C++ (and other native langs), and also contains a bunch of links to more advanced articles relevant when you need to get the best performance from a machine.

The most important tools are callgrind (from valgrind suite) and perf. On Debian-family Linuxes these can be installed with:

sudo apt-get install valgrind linux-tools-common


sudo apt-get install valgrind linux-tools-generic

Frame-based profiling with D ranges

In previous posts I wrote about frame-based profiling and how to do it with reasonable memory usage. Tharsis.prof is the library (in D) I wrote based on these ideas. It provides minimalist RAII-based API for recording overhead of zones. However, the more interesting part of Tharsis-prof is the range-based API used to process profiling data.

Ranges in D

A lot has been written about D ranges, but for our needs it should be enough to say that ranges are a couple of compile-time concepts describing sequences.

For example, a random number generator that generates numbers in a sequence can be used as an input range, a single-linked list can be iterated with an forward range range that in an input range that can save its current position into a copy, a doubly-linked list can use a bidirectional range (which is a forward range) and so on.

D libraries use compile-time predicates to determine that something is a range. For example, isInputRange!T checks that type T has E front(), void popFront() and bool empty() members where E is the element type. These range primitives provide one-directional iteration or generation functionality of an input range. Any type that defines these primitives is considered a range. Since this is checked at compile-time, there is no need to use virtual functions, allowing the compiler to inline the range methods (assuming a compiler with a decent optimizer, such as GDC or LDC).

D standard library and many third-party libraries are designed to work with ranges. For example, filter from std.algorithm takes any input range and returns a new range type that will only iterate over filtered results from the input range. map and reduce from the same module do the same thing their namesakes from other languages do, std.range.cycle takes a forward range and wraps it in an infinite circular range, etc.

Tharsis-prof ranges

Tharsis-prof uses range types to wrap profile data (which is a raw byte buffer, see previous post) in a type-safe API. D standard library (especially std.algorithm) can then be used to analyze this data.


EventRange is a simple forward range that wraps raw profile data. Elements of this range are of type Event; a simple struct that specifies event type (such as ZoneStart or ZoneEnd), time when the event occured and any event-specific data. EventRange is not not very useful by itself; it serves as a low-level base for other ranges. It does, however, provide a lightweight type-safe API to read profile data. Implementation of EventRange is very simple; it just needs to remember its position in the profile data buffer (updated in popFront()) and read the bytes at this position to produce an Event (in front()). Events produced by EventRange are ordered by time.

Usage is pretty straigforward:

// "profiler" is a Profiler we used with some Zones to get some profiling data.
// Profiler profiler;

import std.stdio;
// Create an EventRange from profile data with UFCS syntax.
auto events = profiler.profileData.eventRange;
// Foreach over range calls popFront()/front()/empty() internally
foreach(event; events)

import std.algorithm;
// Get a range of only the events with start time between 1000 and 5000 (hectonanoseconds)
// (std.algorithm.filter)
// This line doesn't filter anything or allocate memory; filtering only happens once
// "filtered" is iterated over (but if we did want to do the filtering right now, e.g.
// to get an array of filtered results, we'd suffix this with ".array")
auto filtered = events.filter!(e => e.startTime > 1000 && e.startTime < 5000);

// Print the IDs of events between 10000 and 50000 hectonanoseconds
// (std.algorithm.map, although admittedly we could just print event.id for each event)
foreach(id; filtered.map!(e => e.id))

// Count the number of events between 1000 and 5000 (std.algorithm.count)


ZoneRange is a forward range that wraps an EventRange, or rather any forward range of Event. Like EventRange, it’s a lightweight wrapper that generates elements on-the-fly; it generates ZoneData structs which specify the time and duration of a Zone, zone info string and ID of its parent zone. Unlike EventRange, ZoneRange does need some extra memory to store the stack of parents of the current zone. ZoneData generated by a ZoneRange are sorted by their end time (the ZoneRange must read both zone start and end events to construct ZoneData).

ZoneRange allows us to get some more interesting data from a profiling run, such as all zones in the longest/slowest frame (which is a likely place to find what causes any lag):

// Profiler profiler;

import std.algorithm;
// zoneRange() internally builds an EventRange and wraps it in a ZoneRange.
auto zones = profiler.profileData.zoneRange;
// filter! produces a range of only the zones with the string "frame" as info,
// which is the info string we passed to Zone instances used to wrap the entire frame.
auto frames = zones.filter!(z => z.info == "frame");

import std.container;
// std.container.Array constructor builds an RAII array containing zones from frames.
// We need an array as we need a random access range to sort the zones.
auto frameArray = Array!ZoneData(frames);
frameArray[].sort!((a, b) => a.duration > b.duration);

import std.stdio;
// Print durations of 4 longest frames.
foreach(frame; frameArray[0 .. 4])

// Print details about all zones in the worst frame.
auto worst = frameArray[0];
foreach(zone; zones.filter!(z => z.startTime >= worst.startTime && z.endTime <= worst.endTime))
    writefln("%s: %s hnsecs from %s to %s",
             zone.info, zone.duration, zone.startTime, zone.endTime);


accumulatedZoneRange is a function that returns a range of a Voldemort type that can not be named directly. In fact, the type will differ based on input parameters. accumulateZoneRange takes one or more forward ranges of ZoneData and uses functions (accumulate and match) passed as compile-time parameters to merge matching zones and accumulate data about them. match, by default, considers zones with the same info to be a match, which will merge them if they share a parent or their parents have been merged. One use of this is to get total or average time spent in each zone between frames, similarly to a conventional profiler, but depending on the accumulate function different data can be calculated. For example, the maximum duration of any matching Zone (to get not just the longest frame but the longest time for every zone in all frames), the number of times a zone was entered, and so on.

Element type of range returned by accumulateZoneRange depends on accumulate, and consists of a ZoneData and the return value of accumulate, whatever it may be. For example, you could use accumulate with a struct return type to accumulate multiple values simultaneously.

The match function could be changed, for example, to merge zones or keep them separate based on their info, or to merge similarly named zones (e.g. numbered draw calls).

Unlike ZoneRange, accumulateZoneRange (the function itself, not returned type) needs a decent chunk of memory (enough to copy all zones from the input zone ranges). This must be provided by the user. See example (sorry for the gray text, the highlighter apparently doesn’t know @nogc yet):

// Accumulate data into this struct.
struct ZoneInfo
    ulong minDuration;
    ulong maxDuration;
    // Needed to calculate average duration.
    size_t instanceCount;

    // We also need the total duration to calculate average, but that is accumulated
    // by default in zoneData.

// Gets min, max, total duration as well as the number of times the zone was entered.
ZoneInfo accumulate(ZoneInfo* aPtr, ref const ZoneData z) pure nothrow @nogc
    if(aPtr is null) { return ZoneInfo(z.duration, z.duration, 1); }

    return ZoneInfo(min(aPtr.minDuration, z.duration),
                    max(aPtr.maxDuration, z.duration),
                    aPtr.instanceCount + 1);

auto zones      = profiler.profileData.zoneRange;

const zoneCount = zones.walkLength;
alias Data = AccumulatedZoneData!accumulate;
// We could also do 'new Data[zoneCount]' with the GC, or a safer malloc() wrapper
auto accumStorage = (cast(Data*)malloc(zoneCount * Data.sizeof))[0 .. zoneCount];
scope(exit) { free(accumStorage.ptr); }

auto accumulated = accumulatedZoneRange!accumulate(accumStorage, zones.save);

// Write out the results.
foreach(zone; accumulated) with(zone.accumulated)
    import std.stdio;
    writefln("id: %s, min: %s, max: %s, avg: %s, total: %s, count: %s",
             zone.id, minDuration, maxDuration,
             zone.duration / cast(double)instanceCount, zone.duration, instanceCount);


For now, Tharsis-prof can do what I need it to do: profile my code over short durations of time and allow analyzing results of such short profiling runs in real time. There’s still a lot that could be done to make Tharsis-prof a decent profiler. A tree structure of zones would make analysis of profile data easier in some cases; memory usage can be decreased even further (e.g. by using a lightweight compression algorithm); and of course, a way to visualize profile data (preferably in real time) would be very useful.

I expect to revisit Tharsis-prof eventually, but for now I need to put it in use for Tharsis itself.

Optimizing memory usage of a frame-based profiler

This is a continuation of a previous post about frame-based profiling. This post explains in detail how I went about decreasing memory usage of a frame-based profiler I’m using to measure performance of processes (aka ECS Systems) in Tharsis.

Shaving off bytes

My first frame profiler worked by adding a (64-byte) ZoneStorage struct to a global array whenever a Zone ended. ZoneStorage consisted of the time when the zone was entered/exited (both 8-byte, since I need high precision), a fixed-size info string and some extra data adding up to 64 bytes per zone. This quickly ate through my memory when there were many zones in a frame (e.g. 10000 zones per frame * 30 FPS * 64 bytes == 19.2 MiB/s). I was aware of a trivial way to save some memory (moving the info strings out of ZoneStorage so short strings can really be short), but at the time this was ‘good enough’ as I didn’t need long profiling runs (and profiled on a machine with 16 gigs of RAM).

struct ZoneStorage
    // In seconds since startup
    double start;
    double end;
    // Index of the parent zone
    uint  parent;
    char[43] info;
    // Size of used space in info
    ubyte infoBytes;
    static assert(ZoneStorage.sizeof == 64, "Unexpected size of ZoneStorage");

When rewriting the profiler, I wanted to decrease memory usage as much as possible, especially for heavy workloads with many zones per frame or high FPS. I started by moving the info string to a separate buffer, with simple layout (note: I didn’t want to deduplicate strings as that could consume a lot of CPU time):

ubyte length1
char[length1] infoStr1
ubyte length2
char[length2] infoStr2

This cuts memory per zone to 21 + info.length bytes, which is a decent improvement to start with.

Another ‘obvious’ improvement is to remove parent and use deltas for time. parent can be inferred from zone start and end times. The idea with delta values is to use something like startSincePreviousZone and duration instead of start and end. Shorter time spans like time between zones or zone durations should hopefully need less precision to represent.

It turned out this didn’t work as well. float was out of question due to its lack of precision, so I considered storing hnsec-precise (hectonanosecond, or tenth of a microsecond) time deltas using uint. That doesn’t work either, because a single second has 10M hnsecs which means the longest time delta we could represent is about 430 seconds. Which is OK for most cases, but could blow up unexpectedly from time to time.

Overall, this would decrease the size of a zone to 9 + info.length bytes, which is pretty good, but at the cost of inability to handle big time gaps.

Thinking in Events

To decrease memory usage even further, I had to think about the problem differently. Obviously, I couldn’t make ZoneData any smaller without making the maximum supported time delta too short.

In heavy workloads (say, 10000 zones per frame), we can start thinking in terms of zones per second or time between zones. If we have (10000 * 30FPS) 300k zones recorded per second, average time between zones will be in the order of microseconds, or 10s of hnsecs. That can be represented by a 4 byte uint; in fact it can be represented by a byte. Many zones would not have such short durations, because zones are hierarchical. However, most time gaps between individual zone starts and ends would be so short.

Zone can be separated into zone start and zone end: events occuring in separate points in time. Instead of a ZoneData array, we can store profiling data as a stream of such events, represented by an event type ID followed by event-specific data. For example, a ZoneStart/ZoneEnd event can use the following format:

EventID (ubyte) EventID.ZoneStart
ubyte timeSinceLastEvent

We can use other events to represent time spans longer than what ubyte can represent as well as zone info (which then becomes a more general ‘info’ event that may be used for more than just zones). With this, zone size in heavy workloads goes down to 2 + 2 + 2 + info.length bytes, although this size will increase in lighter loads where time gaps between events can’t be represented by the ubyte in zone events.

A basic time span event would take 3 or 5 bytes (EventID + ushort``||``uint). However, in heavy workloads where there are few hnsecs between events, 2 or 4 byte precision may be more than needed. Usually we just need an extra bit to make the time gap slightly longer than the 255 hnsecs representable on byte in a ZoneStart or ZoneEnd event. I ended up using time span events that specify fixed-length time spans (256 or 65536 hnsecs). These can be stored in 1 byte (event ID). This leads to unnecessary overhead in light workloads, but it turns out to be not that much (550 kiB/h in ‘idle’), and can be minimized by also adding an event type that specifies exact time span.

The event stream itself is a simple array of bytes. The events described above have an extra benefit of using 1-byte values for everything, removing any need to deal with endianness. The event stream is still written to by an RAII Zone struct. Example fragment of an event stream:

EventID (ubyte) EventID.LongTimeSpan  
EventID (ubyte) EventID.TimeSpan  
EventID (ubyte) EventID.ZoneStart start of zone “first”
ubyte timeSinceLastEvent  
EventID (ubyte) EventID.Info  
ubyte infoLength 9
char[infoLength] info “first”
EventID (ubyte) EventID.TimeSpan  
EventID (ubyte) EventID.ZoneEnd end of zone “zone1”
ubyte timeSinceLastEvent  
EventID (ubyte) EventID.ZoneStart start of zone “second”
ubyte timeSinceLastEvent  
EventID (ubyte) EventID.Info  
ubyte infoLength 10
char[infoLength] info “second”
EventID (ubyte) EventID.ZoneEnd end of zone “second”
ubyte timeSinceLastEvent  
EventID (ubyte) EventID.TimeSpan  

Having a linear stream of events also naturally lends itself to wrapping in a range-style API to generate type-safe event objects on-the-fly and use the full power of standard D modules like std.range and std.range. I’m writing about that in the next post.

Frame-based game profiling

To make Tharsis multi-threaded, I need to be able to balance the overhead of individual processes (called Systems in many other ECS frameworks) by moving them between threads. To do this, I need to quickly measure the time spent running each process on a frame-by-frame basis.

When working on ICE, I wrote a simple frame-based profiler that recorded exact time when entering and exiting important zones of code, such as rendering or GUI. I wanted to update this profiler for Tharsis, but it turned out to be easier to rewrite it due to heavy use of globals (not friendly to threaded code) and occasional slow allocations that distorted the output.

I also intended to write a blog post about it... but it got a bit too long so I divided it into three shorter posts. This post is an introduction to what frame-based profiling even is, or rather, what I mean by it. It’s likely there’s a more common term than frame-based profiling, but I didn’t come across it yet.

Other parts of this series:

Main-stream profilers are not always helful (for games)

Profilers such as perf, CodeXL and even Callgrind+KCacheGrind are great for finding the slowest parts of your code in average case. With a good profiler you can identify bottlenecks on source line or instruction level, count cache misses, branch mispredictions and so on. This is awesome to improve the average performance of your game... and pretty useless when you get that two-second heisenlag that only happens once per hour.

With a conventional profiler, irregular spikes in overhead disappear as they are averaged over time. To track down such spikes, we need a profiler capable of keeping track of individual frames.

Frame-based profiler

A frame-based profiler records overhead for each frame separately. An external tool can’t really know what ‘frame’ means (especially if ‘frame’ is decoupled from rendering), so we have to instrument our code manually.

Once we know the duration of each frame, we can determine when any unexpected overhead happens. If we know how long various parts (zones) of each frame take, we can look at zones of slowest frames to find offending code.


A zone is a section of code to measure overhead of. Zones may contain child zones (e.g. a “rendering” zone may contain “batch” sub-zones). In D or C++ we can implement this with a RAII type that records the precise time in its constructor and destructor. This can be pretty easy to use:

auto profiler = new Profiler(new ubyte[1024 * 1024 * 16]);
    Zone frameZone = Zone(profiler, "frame");

        Zone renderZone = Zone(profiler, "render");

        // ... rendering code ..

        // renderZone end
        Zone collisionZone = Zone(profiler, "collision");

        // ... collision code ..

        // collisionZone end
        Zone aiZone = Zone(profiler, "AI");

        // ... AI code ..

        // aiZone end

    // frameZone end


To get precise time, use a high resolution clock such as std.datetime.Clock.currStdTime in D (also see C (POSIX), C++11)


Unlike a conventional profiler, a frame-based profiler can’t measure each function, line or instruction.

Frame-based profiling doesn’t replace a profiler; however, it can detect issues such a profiler could not. Once you’ve localized the issue, you may even isolate it and use e.g. a sampling profiler to optimize it.

Note that keeping track of profiling data can be pretty memory intensive. The next post is about how I minimized the overhead of the frame profiler I wrote for Tharsis.

UltiSnips, snippet design and GIFs

Some time ago I made D snippets for UltiSnips. It was my first real attempt to write snippets for more than just my own projects, and it didn’t go without issues. The snippets weren’t very composable, some didn’t save all that many keystrokes, there were various bugs (especially with code wrapping and indentation), etc.

I continued to improve those snippets on the side as I write more D code, and recently I’ve been trying to get them to releasable state that could hopefully be merged back into the default snippets in near future (after some exposure to the D community).

The result is now on GitHub, including a reference with GIFs demonstrating each snippet. The new snippets are more “smart”, taking advantage of Python interpolation and handle various corner cases where the older snippets weren’t useful. The snippets file is now much larger, but should be more readable for anyone interested in improving them. While much of it is D specific, I think some ideas may be useful to improve snippets for other uses. BTW... those GIFs took a surprising amount of time. More on that below.

animation demonstrating the try, catch and throw snippets

try, catch and throw used to wrap code in a try/catch with 2 catch blocks.

Snippet guidelines

While working on DSnippets I came up with some basic guidelines on how to design a snippet. Many of these are obvious but may be easy to miss when hacking together snippets for a new project.

These guidelines refer to various D snippets in DSnips, but they are useful regardless of what language you use.

  • Use short triggers for snippets that will be used frequently (as - assert(), wr - writeln()) or if the trigger is unlikely to collide with autocompletion (sw - switch).

  • Use long triggers for snippets that will be used less often, but where a snippet can still save a lot of typing (opApply).

  • Regexp triggers are awesome... but they don’t work with autocomplete (YouCompleteMe).

    At first, most of my snippets used regexps for aliases (e.g. sw|switch). This was convenient (a user could use a snippet even after missing it by writing too many characters), but autocompletion makes it easy to discover and remember snippets. It may also be hard to get the less trivial regexps just right to avoid collisions. Now I only use regexps when necessary (e.g. DDoc as there are multiple comment styles).

  • A frequently used snippet helps even if it saves only a few keystrokes. On the other handle, if you use a snippet only once a month, saving 3 keystrokes out of 50 is not worth it.

  • To counter the previous point, even rarely used snippets can help to avoid forgetting things or to write good code.

    Operator overloading snippets such as op[] ir op$ generate a function with a correct name and can check if its signature is valid for the operator. Snippets can also help with documentation (e.g. Params: in D when defining a function; same could be done with C++ and Doxygen.

Minimize keystrokes

  • Check whether a snippet really saves keystrokes.

    Compare the number of keystrokes when typing code manually vs. with a snippet. Macros can help: qw<code>q"wp will get you all characters typed in <code>. Keep in mind that certain characters are more “difficult”, especially when holding a modifier such as Shift.

  • Sometimes, a snippet only saves keystrokes when used to wrap code with $VISUAL. Wrapping code is usually more intuitive than indent this code, surround it, move to top, add a header.

  • It is useful to have help information as the default contents of a tabstop, e.g. $1{/*loop counter*/}. With a few exceptions:

    • If the user may want to keep the tabstop empty (e.g. function params), having a default would require one more keystroke (<Backspace>) to delete the default.
    • If there is an obvious default (e.g. void for function return types), using it will save keystrokes.
  • Don’t forget to use ${0} as the last tabstop (where it makes sense). Upon reaching ${0}, UltiSnips considers a snippet to be done.

    If a snippet without ${0} is used in a tabstop of another snippet, trigger must be pressed twice to end the inner snippet; once to exit the inner snippet and once to move to the next tabstop of the outer snippet. With ${0} UntiSnips knows that the inner snippet is done and will move to the next tabstop of the outer snipper directly.

Snippets should work together

  • Tabstops should be placed (where possible) to enable other snippets to be used within the tabstops.

    For example, the module snippet contains a tabstop for the license header, which has default contents (Boost license) but can be changed to GPL simply by typing gpl<Trigger>.

  • The final tabstop (${0}) should place the cursor somewhere useful, even for one-liners.

    For example, imp - import puts the cursor on the next line so imp can be used again to import another module, try places the cursor so catch can be used immediately after (and catch places the cursor so another catch can be used...), same for sw - switch / case and so on.

  • Snippets should be usable from within other snippets when it makes sense (or rather, should be usable anywhere that makes sense). E.g. snippets for expressions that return a value such as format, tup - tuple, new should not be followed by a newline or a semicolon.

Recording Vim GIFs

While I often see articles with GIFs showing off Vim, it’s been surprisingly difficult hard to find a convenient way to record GIFs en masse. DSnips has tens of snippets and recording each one with an ffmpeg script would be way too cumbersome.

I wanted to find a recording tool that could:

  1. Start/stop with a keypress, without having a window open in the foreground
  2. Record arbitrary screen area
  3. Run on Linux
  4. Output GIFs (or something I can trivially batch convert to GIFs)

Unfortunately I didn’t find that tool. (I still think I might be missing something obvious.)

What I did find was Byzanz, which can be used from command-line and can be found in Debian/Ubuntu/Mint repos:

sudo apt-get install byzanz

Byzanz can do #2, #3 and #4, but can’t really do #1 (recording duration is fixed and passed as a parameter to the byzanz-record command).

Since it’s usable from the command line, I was able to at least create a few simple mappings to launch Byzanz (you might need to modify these if you want to use them):


  • nnoremap <Leader>rqq :!byzanz-record<space>-w<space>560<space>-h<space>80<space>-d<space>24<space>gvim.gif&<CR><CR>
  • nnoremap <Leader>rwq :!byzanz-record<space>-w<space>560<space>-h<space>160<space>-d<space>30<space>gvim.gif&<CR><CR>
  • nnoremap <Leader>rWq :!byzanz-record<space>-w<space>560<space>-h<space>240<space>-d<space>48<space>gvim.gif&<CR><CR>
  • nnoremap <Leader>rEq :!byzanz-record<space>-w<space>560<space>-h<space>320<space>-d<space>64<space>gvim.gif&<CR><CR>
  • nnoremap <Leader>rRq :!byzanz-record<space>-w<space>560<space>-h<space>480<space>-d<space>96<space>gvim.gif&<CR><CR>

These launch Byzanz with increasing recording times and an enlarging screen area (starting at the top/left corner of the screen). The recorded image is written to gvim.gif in the working directory. The times are longer than needed to type the snippets; an overly long GIF is better than one that ends abruptly in the middle of typing.

I ended up with a bunch of GIFs that were too long; half the time it seemed as if they were showing a code example, not a snippet usage example. I used Gimp to shorten them; Gimp opens a GIF as an image with one layer per frame, and the name of a layer specifies its duration. Most of the GIFs had a last frame taking multiple seconds, so they could be cut simply by deleting its layer.

In the end, these GIFs took way too much time, definitely more than I expected (more than a day). Maybe in future I’ll write a minimal keyboard-controlled screen recorder. With a Vim plugin. Or maybe not; too much stuff to do already.