Hey all, today we’re going over Depot and how to use it as a basic database for your Haxe Project. This is something I use for m own games, and it has been instrumental in creating enemies, items, dialogue, and more easily. Let’s get into it.

What Is Depot?

Depot is a flat-file data storage extension that works with Visual Studio Code. It allows you to store data in a JSON format, but add data visually like an Excel or Google spreadsheet. Here’s an example of the layout!

Layout Example Image

For more information, here’s a video on the technology!

So now that we know what it is, how do we mak it work for us?

Haxe Black Magic

In Haxe, there is a set of tools to create macros. A macro is a piece of code that runs before the code is compiled for the final program output. This allows you to bake code into your program like black magic. For example, we could bake in the result of parsing a certain Depot File.

Database References In Code

By using macros we can create references in the code by adding the result of parsing the file to the source code itself. This is how macros work in Haxe and allows us to do things we usually can’t in several languages. So, how do we do it with a Depot File? See below, it’s a pretty crazy macro.

package macros;
#if macro
import haxe.DynamicAccess;
import Types.DepotFile;
import sys.io.File;
import sys.FileSystem;
import haxe.macro.Expr.Field;
import haxe.Json;
import haxe.macro.Context;
import haxe.macro.Expr;
import haxe.macro.Type;
using haxe.macro.Tools;
#end
import haxe.extern.EitherType;
using Lambda;
using StringTools;
using ext.StringExt;
// Depot Information
typedef DepotFile = {
var sheets:Array<DepotSheet>;
}
typedef DepotSheet = {
var name:String;
var description:String;
var displayColumn:String;
var guid:String;
var columns:Array<ColumnInfo>;
var lines:Array<LineInfo>;
var hidden:Bool;
var configurable:{
var name:String;
var description:String;
var displayColumn:String;
}
var ?parentSheetGUID:String;
var ?columnGUID:String;
}
typedef ColumnInfo = {
var typeStr:TypeString;
var guid:String;
var name:String;
var description:String;
var defaultValue:String;
var sheet:String;
var iconName:String;
var configurable:ConfigInfo;
}
typedef ConfigInfo = {
var name:String;
var description:String;
var defaultValue:EitherType<String, Array<Dynamic>>;
}
typedef LineInfo = {
var guid:String;
var id:String;
}
enum abstract TypeString(String) from String to String {
public var TEXT = 'text';
public var LONGTEXT = 'longtext';
public var BOOL = 'bool';
public var INT = 'int';
public var FLOAT = 'float';
public var IMAGE = 'image';
public var LIST = 'list';
public var ENUM = 'enum';
}
// using StringExtensions;
/**
* Contains the build macros for creating Depot
* integration within the Haxe programming language.
*/
class DepotMacros {
#if macro
public static macro function buildDepotFile(filePath:String):Array<Field> {
//Get the fields on the associated class
var buildFields = Context.getBuildFields();
if (FileSystem.exists(filePath)) {
var fileData = File.getContent(filePath);
var depotData:DepotFile = Json.parse(fileData);
// Depot Starts from inside the data element
//Iterates through each sheet
depotData.sheets.iter((sheet) -> {
var dynamicSheet:DynamicAccess<Dynamic> = cast sheet;
dynamicSheet.remove('columns'); // Remove unnecessary columns
var cleanName = ~/!|\$||\s+/g.replace(sheet.name, '_')
.capitalize();
var sheetFields = new Map<String, ObjectField>();
for (key => value in dynamicSheet) {
var cleanKey = ~/!|\$||\s+/g.replace(key, '_');
// Handles Finding Important Fields
var endVal = macro $v{value};
var includeField = true;
switch (endVal.expr) {
case EObjectDecl(fields):
endVal = {
expr: EObjectDecl(fields.filter((field) -> {
return switch (field.expr.expr) {
case EArrayDecl(values):
values.length > 0 ? true : false;
case _:
true;
}
})),
pos: Context.currentPos()
};
// Start Of Lines and other array declarations
case EArrayDecl(values):
if (values.length < 1) {
includeField = false;
} else {
var newValues = [];
values.iter((arrExpr) -> {
// trace(arrExpr);
switch (arrExpr.expr) {
case EArrayDecl(values):
if (values.length > 0) {
newValues.push(arrExpr);
}
case EObjectDecl(fields):
// Creates the lines in the DepotData
var lineName = '';
var newDecl = EObjectDecl(fields.filter((field) ->
{
return
switch (field.expr.expr) {
case EArrayDecl(values):
values.length > 0 ? true : false;
case _:
true;
}
})
.map((field -> {
field.field = ~/!|\$||\s+/g.replace(field.field,
'_');
// Name Individual Lines
lineName = field.field.contains('name')
&& lineName == '' ? field.expr.toString() : lineName;
return field;
})));
// Final Result for the line
var objExpr = {
expr: newDecl,
pos: Context.currentPos()
};
// Take Result and Convert to individual element
var valueComplexType = Context.toComplexType(Context.typeof(objExpr));
var cleanLineName = lineName.replace("\"",
"");
cleanLineName = ~/!|\$||\s+/g.replace(cleanLineName,
"_");
var newField:Field = {
name: '${cleanName}_${cleanLineName}',
pos: Context.currentPos(),
kind: FVar(valueComplexType,
objExpr),
access: [Access.APublic, Access.AStatic]
};
// Push line as a class property with sheet prefix
buildFields.push(newField);
// Push Lines to their individual Sheets
newValues.push(objExpr);
case _:
newValues.push(arrExpr);
}
});
endVal = {
expr: EArrayDecl(newValues),
pos: Context.currentPos()
}
}
case _:
// Do nothing
}
// Includes a Field in the sheet, such as lines
if (includeField) {
sheetFields.set(cleanKey, {
field: cleanKey,
expr: endVal,
});
}
}
var valueExpr = EObjectDecl(sheetFields.array());
var result = {expr: valueExpr, pos: Context.currentPos()};
var valueComplexType = Context.toComplexType(Context.typeof(result));
var newField:Field = {
name: cleanName,
pos: Context.currentPos(),
kind: FVar(valueComplexType, result),
access: [Access.APublic, Access.AStatic]
};
buildFields.push(newField);
});
}
//Returns the fields of the class with the newly added elements included.
return buildFields;
}
#end
}
view raw DepotMacros.hx hosted with ❤ by GitHub

This macro splits a Depot file into several sheets similar to excel and goes through the structure of each one to create a type for that sheet. Because, Depot sets up everything as an individual spreadsheet, parsing the information is straightforward. The rest of the macro is ignoring irrelevant information that we don’t need for our static data.

Now that we’re done parsing, we can use the macro like so:

package;
// Path to your own depot file
@:build(macros.DepotMacros.buildDepotFile('assets/data/database.dpo'))
class DepotData {}
view raw BuildMacroExample.hx hosted with ❤ by GitHub

This then adds all the fields from the Depot data directly to the class itself. In Haxe this is known as a build macro and is extremely useful for things like this.

Now, here’s the final result of our macro being used in Visual Studio Code:

Now we can reference any of the database data in our code without any hassle of looking up the data from a map or table through the Visual Studio Code Intellisense/autocomplete.

Conclusion

I hope this post helps you! With that said, good luck with creating your game. If you’d like to use the macro yourself, you can find a single file version here or copy the example above!

%d bloggers like this: