Calibre “read next” query

I read a lot, and a common problem I have is keeping track of the series I’m currently reading. For a while I’ve toyed with the idea of writing a Calibre plugin that would allow me to see books that belong to a series which I’ve started but haven’t caught up with (I rate books as I finish them, but parsing my library at a glance to see what comes next is difficult). Today I found the Multi-Column Search plugin and it turns out to have exactly what I need to solve this problem: a freeform SQLite field.

select id from (
  select *,
    row_number() over(
      partition by series order by series_index asc
    ) row_num
  from (
    select
      b.id, bsl.series, b.series_index, r.rating
    from books b
    inner join books_series_link bsl on b.id = bsl.book
    left join books_ratings_link brl on b.id = brl.book
    left join ratings r on brl.rating = r.id
  ) where rating is null
)
where row_num = 1 and series_index > 1

This script will filter the library to include the next unread (unrated) book in your library from each series that has at least one book that has been rated. It’s a little uglier than I would have liked, since I had to use one long query instead of CTEs, and I would have liked to be able to return them in order (maybe by overall average score), but due to limitations in the plugin this is as good as it’s going to get. Even so, I can tell it’s going to be super useful.

YMD Revisited

In Year, Month, Day, I explained an annoying problem with Javascript dates, and outlined the solution I took to avoid it in Moyase (which only needs to deal with dates in local time, and shouldn’t care if you move your device across timezones). Well, as it turns out, even this solution was not foolproof, and I totally should have seen it coming.

See, Date.toISOString() does indeed return a pretty nicely-formatted string that I can carve up and use for date components…the (obvious, in retrospect) problem is that it gives it to you in UTC time with a timezone offset, which can very well be a totally different calendar day than in local time. I probably would have never noticed this until I found myself on the other side of the world at some point in the future, or happened to be working in the app very early in the morning, so I’m very lucky to have a friend in Australia as an early adopter; she noticed that at certain times in the day, her work would be counted towards the previous day and her calendar wouldn’t display the right dates.

The fix ended up being pretty simple; instead of this:

return date.toISOString().substr(10);

I went with this instead:

return [
    String(date.getFullYear()).padStart(4, '0'),
    String(date.getMonth() + 1).padStart(2, '0'),
    String(date.getDate()).padStart(2, '0'),
].join('-');

With this, the app works correctly no matter the timezone or time of day. Hopefully this is the last time I have to think about this problem.

Year Month Day

5…4…3…2…1…Happy New Year!

It’s midnight, January 1st, 2021. The ceremonial ball has dropped in Times Square and 2020 has just come to a close. As the festivities begin to wind down, your phone buzzes. It’s a text from your friend in Los Angeles.

“My wife just delivered our baby! It’s a boy!”

What is the baby’s birthday?

Continue reading →

The nested factory pattern

When writing code, I try to structure my project in such a way that it’s impossible to misuse. I often run into cases where I need some class to be responsible for instantiating and managing its own resources, such as a message hub (which needs to maintain a list of subscribers) or a tweening engine (which would need to update and dispose of tween control objects according to a timer). In these cases, I like to ensure that these objects can never be created outside the class which manages them.

A simple solution, of course, is to use internal constructors, but I personally prefer to avoid using the internal access modifier whenever possible, as I find it does a poor job of signalling the intent of the code, and in many cases is just a band-aid fix for a poorly planned access scheme. Furthermore, in cases where the class in question will only be consumed within the same project, internal is essentially no different than public.

My solution to this problem is as follows:

Continue reading →

Haxe still has problems

Following up from my post about using Haxe/OpenFL to save some of my old Flash games, I do feel like I have to talk about some frustrating points that came up during the progress.

I will preface this by saying that the problems Haxe has now are different than the problems it had when I was using it previously. Throughout this process, I only came across a bare few instances where switching targets led to suddenly broken code. The OpenFL runtime never totally crashed on me like it used to do. I didn’t have to fiddle with DCE flags to prevent my code from being erroneously stripped from the build. It’s obvious that Haxe has improved a lot over the past few years, and that’s great. I do plan on continuing to use it to port my other game projects, and I would recommend it to anyone else for the same purpose. I do believe in criticizing the things we love though, so let’s get into it.

Continue reading →

Glide update: Tween overwriting

I’ve just added support for property overwriting to Glide, my Tweening engine for C#. This functionality exists in Actuate (a great Haxe library from which I’ve taken a lot of inspiration) and I’ve wanted it in Glide for a while now because it’s just so darn handy.

Imagine you start a tween off on its way:

// move image to (100, 200) over five seconds
Tweener.Tween(image, new { X = 100, Y = 200 }, 5);

…but then something changes, and now you want your image to travel instead to (X = 500) instead, and it should only take two seconds:

Tweener.Tween(image, new {X = 500}, 2);

At first this will work fine, but when the second tween finishes, suddenly the first one takes precedence again and your image will keep moving towards (X = 100). You could cancel the first tween, but that would also cancel the movement towards (Y = 200).

Now, creating a new Tween on an object that’s already being tweened will cancel the movement of any properties that are currently being tracked. In the example above, the new X tween would overwrite the old one, the original Y tween would keep on working, and there’s no need to manually cancel anything.

The old behavior is still available; just pass false to the overwrite parameter. I’m pretty sure that won’t be necessary in the majority of cases, but it does exist if you want it.

Glide update: Structs don’t work that way

Glide has seen some modest adoption despite the fact that I’ve barely made any effort to get the word out. A handful of developers have contacted me thanking me for the project and asking for help, which I’m always glad to offer. One issue in particular has come up a few times recently, and it relates to the way .NET handles structs.

Structs are passed and returned by value, not reference. This means that when you assign an instance to a variable, that new variable has no relationship to the original object; any properties set on it will only apply to that variable. This is a problem in Glide, since I have to store a reference to the tween’s target object in order to apply the transformations. Worst of all, while normally this issue would be caught at compile time, it’s impossible to detect when you’re using reflection to set values, and the resulting behavior was that the operation would appear to silently fail.

Today I made some changes that should help users realize when they’re attempting to tween an incompatible type. Passing an instance of a struct to the tweener will result in a compile-time error, and if you sneakily box your variable into an Object it will throw an exception at runtime. This still doesn’t solve the problem of how to tween these types of properties, but I’ve got you covered on that front as well.

Glide already has support for extension classes that allow you to add specialized behavior for tweening your own types, but this feature wasn’t really documented anywhere. Now it is! I’ve added a wiki to the Glide Bitbucket repository with an example showing how to tween an XNA Vector2. It should be pretty simple to modify for your own needs…maybe a StringTyper extension that causes letters to show up over time like a typewriter, or a GlitchString that fills the whole string with garbage and slowly fills in the correct letters.

Stuck Scripting overhaul

Early on in the development of Slide (before Iridescence had come to be), I added support for small script files to allow levels to contain more complicated puzzles without running into false positives that caused the reset button to show up while the level was still perfectly solvable. I was using C# at the time, and options for proper scripting languages on the .NET platform are shamefully limited, so I rolled my own.

Here’s a sample of what it looked like:

R > B;
G > B;
B > G ! B -> [B];
B > R ! B -> [B];

The use-case for stuck scripts is extremely specific, so the language itself didn’t need too many bells and whistles. All I needed to do at any time was compare the number of game objects of a given color, and check if pawns had a valid path to their targets. The snippet above basically translates like so:

The puzzle is unsolvable if any of these are true:

there are more red pawns than blue
there are more green pawns than blue
there are more blue pawns than green, unless all blue pawns can reach a target
there are more blue pawns than red, unless all blue pawns can reach a target

Needless to say, it’s as ugly as it is functional, and adding new features proved difficult. The inflexibility of the original design meant for some truly impressive hijinks as I tried to implement more complex behaviors with only the original syntax.

Today I stripped the whole system out and replaced it with a new one. Since I’m using Haxe for this remake, I was able to take advantage of hscript, a subset of Haxe that allows for runtime interpretation. Now, the script above looks like this:

var reds = count(Pawns.Red);
var greens = count(Pawns.Green);
var blues = count(Pawns.Blue);

if (reds > blues) fail();
if (greens > blues) fail();

if (blues > greens && !hasPathToExit(Pawns.Blue))
	fail();

if (blues > reds && !hasPathToExit(Pawns.Blue))
	fail();

Much better.

The original scripting system was designed as a black box, with a minimal number of inputs and outputs. By keeping the interface consistent between implementations, I was able to completely change the underlying code without changing any other aspect of the program. It’s nice to be able to change something so integral to the gameplay without worrying about side effects.

Glide update: From()

I made a small addition to Glide, my tweening library for C#. You can now specify starting positions for the variables you’re tweening, useful for when you need to change a variable instantly and then tween back to the default value. I use this when I’m making things flash for alerts or other important things for the player to notice.

tweener.Tween(sprite, new { Scale = 1 }, 1)
    .From(new { Scale = 1.5f });

Obviously you could just set the values manually before telling them to tween, but this is a nice shortcut if you’re setting a lot of them at once.

You can download Glide here.

Haxe macros are amazing

Here’s a neat trick I discovered today while working on Iridescence.

Haxe has native macros that are very different from those found in C; rather than using a separate pre-processing language, Haxe macros are simply snippets of Haxe code that are run at compile time instead of run time.

In native builds, Iridescence populates its soundtrack dynamically by searching for all song files in the /assets/music folder. On more restrictive platforms like Flash, all assets have to be known at compile time so they can be embedded in the SWF, which meant I needed to create a list of files manually and update it whenever I added a new song. I wrote this little macro to iterate through the music folder at compile time and build a list of song files automatically.

macro public static function getSongList(extension:String)
{
	var songs = FileSystem.readDirectory("assets/music")
		.filter(function(s) return s.endsWith(extension))
		.map(function(s) return "assets/music/" + s)
		.array();

	return Context.makeExpr(songs, Context.currentPos());
}