Last active
September 11, 2016 03:51
-
-
Save gamedevsam/ea06ae3b57fcdf3a4048 to your computer and use it in GitHub Desktop.
Haxe Toml Configs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package ; | |
import flixel.FlxG; | |
import flixel.addons.nape.FlxNapeState; | |
import toml.TomlConfig; | |
/** | |
* ... | |
* @author Sam Batista | |
*/ | |
// Typedef configs so we get instant code completion in FlashDevelop | |
typedef DebugConfig = { | |
SHOW_DEBUGGER:Bool, | |
SHOW_DEBUG_OUTLINES:Bool, | |
SHOW_NAPE_OUTLINES:Bool, | |
}; | |
typedef PlayerConfig = { | |
BODY_MASS:Int, | |
RUN_DRAG:Int, | |
MAX_VELOCITY:Int, | |
POLE_LOWERING_DURATION:Float, | |
POLE_ENGAGE_IMPULSE:Int, | |
POLE_LAUNCH_IMPULSE:Int, | |
POLE_FRAGMENT_LENGTH:Int, | |
NUM_POLE_FRAGMENTS:Int, | |
}; | |
typedef ControlsConfig = { | |
PLAYER_SETTING:Array<String>, | |
EASY: { | |
RUN_ACCELERATION:Int, | |
}, | |
ADVANCED: { | |
RUN_ACCELERATION:Int, | |
}, | |
}; | |
typedef GameplayConfig = { | |
LEVEL:String, | |
GRAVITY_X:Int, | |
GRAVITY_Y:Int, | |
VELOCITY_ITERATIONS:Int, | |
POSITION_ITERATIONS:Int, | |
FENCE_HEIGHT:Int, | |
FENCE_HEIGHT_INCREMENT:Int, | |
FENCE_DISTANCE:Int, | |
FENCE_DISTANCE_VARIATION:Int, | |
LANE_DISTANCE:Int, | |
LANE_X_OFFSET:Int, | |
}; | |
typedef GraphicsConfig = { | |
FULLSCREEN:Bool, | |
}; | |
class Config | |
{ | |
public static var Debug:DebugConfig = cast { }; | |
public static var Player:PlayerConfig = cast { }; | |
public static var Controls:ControlsConfig = cast { }; | |
public static var Gameplay:GameplayConfig = cast { }; | |
public static var Graphics:GraphicsConfig = cast { }; | |
public static var loaded:Bool = false; | |
public static function loadConfigs() { | |
if (!loaded) { | |
loaded = true; | |
TomlConfig.CONFIG_PATH = "./"; | |
TomlConfig.TOML_CONFIG_FILES = ["Config.toml"]; | |
TomlConfig.PROFILE_LOAD_DURATION = false; // ENABLE FOR PROFILING & OPTIMIZATION | |
TomlConfig.register(Debug, "Debug"); | |
TomlConfig.register(Player, "Player"); | |
TomlConfig.register(Controls, "Controls"); | |
TomlConfig.register(Gameplay, "Gameplay"); | |
TomlConfig.register(Graphics, "Graphics"); | |
Events.addHandler(null, CommonEvents.CONFIGS_LOADED, configsLoaded, true); | |
} | |
TomlConfig.loadConfigs(#if (!FLX_NO_DEBUG) true #end); | |
} | |
public static function configsLoaded(?o) { | |
#if !FLX_NO_DEBUG | |
FlxG.debugger.visible = Config.Debug.SHOW_DEBUGGER; | |
FlxG.debugger.drawDebug = Config.Debug.SHOW_DEBUG_OUTLINES; | |
if (FlxG.state != null && Std.is(FlxG.state, FlxNapeState)) | |
cast(FlxG.state, FlxNapeState).napeDebugEnabled = Config.Debug.SHOW_NAPE_OUTLINES; | |
#end | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# TOML Spec: https://github.com/mojombo/toml/blob/master/versions/toml-v0.1.0.md | |
[Debug] | |
SHOW_DEBUGGER = false | |
SHOW_DEBUG_OUTLINES = false | |
SHOW_NAPE_OUTLINES = false | |
[Graphics] | |
FULLSCREEN = false | |
[Player] | |
RUN_DRAG = 1000 | |
BODY_MASS = 75 | |
NUM_POLE_FRAGMENTS = 4 | |
POLE_FRAGMENT_LENGTH = 25 | |
POLE_LOWERING_DURATION = 0.15 | |
POLE_ENGAGE_IMPULSE = 1000 | |
POLE_LAUNCH_IMPULSE = 65000 | |
MAX_VELOCITY = 2000 | |
[Controls] | |
# controls settings must match an entry in ControlsMode enum (Player.hx) | |
PLAYER_SETTING = [ | |
"ADVANCED", # setting for lane 0 (player 1) | |
"ADVANCED" # setting for lane 1 (player 2) | |
] | |
[Controls.EASY] | |
RUN_ACCELERATION = 1000 | |
[Controls.ADVANCED] | |
RUN_ACCELERATION = 100 | |
[Gameplay] | |
LEVEL = "test_level.tmx" | |
GRAVITY_X = 0 | |
GRAVITY_Y = 981 | |
VELOCITY_ITERATIONS = 40 | |
POSITION_ITERATIONS = 40 | |
FENCE_HEIGHT = 100 # starting fence height | |
FENCE_HEIGHT_INCREMENT = 25 # consecutive fences get higher by this amount | |
FENCE_DISTANCE = 2000 # objects are layed out every FENCE_DISTANCE pixels | |
FENCE_DISTANCE_VARIATION = 0 # (temporarily disabled) 500 Formula: fence.x = FENCE_DISTANCE + Math.random() * FENCE_DISTANCE_VARIATION; | |
LANE_DISTANCE = 0 | |
LANE_X_OFFSET = 0 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package toml; | |
import haxe.Utf8; | |
import openfl.Assets; | |
import flixel.FlxG; | |
#if cpp | |
import sys.io.File; | |
import sys.FileSystem; | |
#elseif flash | |
import flash.events.Event; | |
import flash.net.URLLoader; | |
import flash.net.URLRequest; | |
#end | |
/** | |
* Sam Batista | |
* @author | |
*/ | |
class TomlConfig | |
{ | |
// list of file names for all configs | |
public static var TOML_CONFIG_FILES = ["Config.toml"]; | |
// path to config | |
public static var CONFIG_PATH = "assets/"; | |
// path to config relative to cpp binaries - REAL_TIME_LOAD | |
public static var CONFIG_PATH_HOT_RELOAD = "../../../../"; | |
// path to config relative to flash swf - REAL_TIME_LOAD | |
public static var CONFIG_PATH_HOT_RELOAD_FLASH = "../../../"; | |
// profile duration of configs load, and print the result to the console | |
public static var PROFILE_LOAD_DURATION = true; | |
//{ PUBLIC | |
// | |
public static function register(obj:Dynamic, ?configName:String):Void | |
{ | |
if (configName == null) | |
{ | |
var objClass = Type.getClass(obj); | |
var className = Type.getClassName(objClass); | |
configName = className.lastIndexOf(".") < 0 ? className : className.substr(className.lastIndexOf(".") + 1); | |
} | |
if(registeredObjects.exists(configName)) | |
registeredObjects.get(configName).push(obj); | |
else | |
registeredObjects.set(configName, [obj]); | |
if (tomlData != null) | |
copyConfigDataToObjects(configName, [obj]); | |
} | |
public static function unregister(obj:Dynamic, ?configName:String):Void | |
{ | |
if (configName == null) | |
{ | |
var objClass = Type.getClass(obj); | |
var className = Type.getClassName(objClass); | |
configName = className.lastIndexOf(".") < 0 ? className : className.substr(className.lastIndexOf(".") + 1); | |
} | |
if(registeredObjects.exists(configName)) | |
registeredObjects.get(configName).remove(obj); | |
} | |
// Config Loading functionality - done every frame if REAL_TIME_EDIT_ASSETS is defined | |
public static function loadConfigs(forceReload:Bool = false):Void | |
{ | |
configModified = false; | |
// Perf Profiling | |
var perfTimer = 0; | |
if(PROFILE_LOAD_DURATION) | |
perfTimer = flash.Lib.getTimer(); | |
// Execute whole block once | |
if (!configsLoaded || forceReload) | |
{ | |
for (c in TOML_CONFIG_FILES) | |
loadConfigFile(c, forceReload, true); | |
if (configModified || forceReload) | |
{ | |
// only dispatch events if the game's been initialized | |
if (FlxG.game != null) | |
{ | |
Events.dispatch(CommonEvents.CONFIGS_LOADED); | |
} | |
// Perf Profiling | |
if (PROFILE_LOAD_DURATION) | |
{ | |
var configDur = (flash.Lib.getTimer() - perfTimer) / 1000; | |
trace("Config Load Duration = " + configDur); | |
} | |
} | |
} | |
configsLoaded = true; | |
} | |
public static function cleanup():Void | |
{ | |
registeredObjects = new Map(); | |
} | |
//} | |
//{ PRIVATE | |
// | |
private static function loadConfigFile(fileName:String, forceReload:Bool, isToml:Bool):Void | |
{ | |
// first time configs must load instantly | |
if (!configsLoaded) | |
{ | |
parseTomlFile(Assets.getText(CONFIG_PATH + fileName)); | |
configModified = true; | |
} | |
else // subsquent times can be asynchronous | |
{ | |
#if !FLX_NO_DEBUG | |
#if flash | |
var loader:URLLoader = new URLLoader(); | |
loader.addEventListener(Event.COMPLETE, function(e:Event) { | |
parseTomlFile(e.target.data); | |
Events.dispatch(CommonEvents.CONFIGS_LOADED); | |
}); loader.load(new URLRequest(CONFIG_PATH_HOT_RELOAD_FLASH + fileName)); | |
#else | |
parseTomlFile(File.getContent(CONFIG_PATH_HOT_RELOAD + fileName)); | |
configModified = true; | |
#end | |
#end | |
} | |
} | |
private static function parseTomlFile(fileData:String):Void | |
{ | |
var localData = TomlParser.parseString(fileData, {}); | |
var configObjs = Reflect.fields(localData); | |
for (objName in configObjs) | |
{ | |
// store loaded config data in master object | |
Reflect.setField(tomlData, objName, Reflect.field(localData, objName)); | |
if (registeredObjects.exists(objName)) | |
copyConfigDataToObjects(objName, registeredObjects.get(objName)); | |
} | |
} | |
private static function copyConfigDataToObjects(dataType:String, objArr:Array<Dynamic>):Void | |
{ | |
var objData = Reflect.field(tomlData, dataType); | |
var objFields = Reflect.fields(objData); | |
for (obj in objArr) | |
{ | |
for (field in objFields) | |
{ | |
Reflect.setField(obj, field, Reflect.field(objData, field)); | |
} | |
} | |
} | |
private static var tomlData = {}; | |
private static var configModified = false; | |
private static var configsLoaded = false; | |
private static var registeredObjects:Map <String, Array<Dynamic>> = new Map(); | |
//} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package toml; | |
using haxe.Utf8; | |
using Lambda; | |
// A Haxe implementation of TOML v0.1.0 language. | |
// obtained from: https://github.com/raincole/haxetoml | |
// TOML v0.1.0 spec: | |
// https://github.com/mojombo/toml/blob/master/versions/toml-v0.1.0.md | |
private enum TokenType { | |
TkInvalid; | |
TkComment; | |
TkKey; | |
TkKeygroup; | |
TkString; TkInteger; TkFloat; TkBoolean; TkDatetime; | |
TkAssignment; | |
TkComma; | |
TkBBegin; TkBEnd; | |
} | |
private typedef Token = { | |
var type : TokenType; | |
var value : String; | |
var lineNum : Int; | |
var colNum : Int; | |
} | |
class TomlParser { | |
var tokens : Array<Token>; | |
var root : Dynamic; | |
var pos = 0; | |
public var currentToken(get_currentToken, null) : Token; | |
/** Set up a new TomlParser instance */ | |
public function new() {} | |
/** Parse a TOML string into a dynamic object. Throws a String containing an error message if an error is encountered. */ | |
public function parse(str : String, ?defaultValue : Dynamic) : Dynamic { | |
tokens = tokenize(str); | |
//for(token in tokens) | |
//trace(token); | |
if(defaultValue != null) { | |
root = defaultValue; | |
} else { | |
root = {}; | |
} | |
pos = 0; | |
parseObj(); | |
return root; | |
} | |
function get_currentToken() { | |
return tokens[pos]; | |
} | |
function nextToken() { | |
pos++; | |
} | |
function parseObj() { | |
var keygroup = ""; | |
while(pos < tokens.length) { | |
switch (currentToken.type) { | |
case TkKeygroup: | |
keygroup = decodeKeygroup(currentToken); | |
createKeygroup(keygroup); | |
nextToken(); | |
case TkKey: | |
var pair = parsePair(); | |
setPair(keygroup, pair); | |
default: | |
InvalidToken(currentToken); | |
} | |
} | |
} | |
function parsePair() { | |
var key = ""; | |
var value = {}; | |
if(currentToken.type == TkKey) { | |
key = decodeKey(currentToken); | |
nextToken(); | |
if(currentToken.type == TkAssignment) { | |
nextToken(); | |
value = parseValue(); | |
} else { | |
InvalidToken(currentToken); | |
} | |
} else { | |
InvalidToken(currentToken); | |
} | |
return { key: key, value: value }; | |
} | |
function parseValue() : Dynamic { | |
var value : Dynamic = {}; | |
switch(currentToken.type) { | |
case TkString: | |
value = decodeString(currentToken); | |
nextToken(); | |
case TkDatetime: | |
value = decodeDatetime(currentToken); | |
nextToken(); | |
case TkFloat: | |
value = decodeFloat(currentToken); | |
nextToken(); | |
case TkInteger: | |
value = decodeInteger(currentToken); | |
nextToken(); | |
case TkBoolean: | |
value = decodeBoolean(currentToken); | |
nextToken(); | |
case TkBBegin: | |
value = parseArray(); | |
default: | |
InvalidToken(currentToken); | |
}; | |
return value; | |
} | |
function parseArray() { | |
var array = []; | |
if(currentToken.type == TokenType.TkBBegin) { | |
nextToken(); | |
while(true) { | |
if(currentToken.type != TkBEnd) { | |
array.push(parseValue()); | |
} else { | |
nextToken(); | |
break; | |
} | |
switch(currentToken.type) { | |
case TkComma: | |
nextToken(); | |
case TkBEnd: | |
nextToken(); | |
break; | |
default: | |
InvalidToken(currentToken); | |
} | |
} | |
} | |
return array; | |
} | |
function createKeygroup(keygroup : String) { | |
var keys = keygroup.split("."); | |
var obj = root; | |
for(key in keys) { | |
var next = Reflect.field(obj, key); | |
if(next == null) { | |
Reflect.setField(obj, key, {}); | |
next = Reflect.field(obj, key); | |
} | |
obj = next; | |
} | |
} | |
function setPair(keygroup : String, pair : { key : String, value : Dynamic }) { | |
var keys = keygroup.split("."); | |
var obj = root; | |
for(key in keys) { | |
// A Haxe glitch: empty string will be parsed to [""] | |
if(key != "") { | |
obj = Reflect.field(obj, key); | |
} | |
} | |
Reflect.setField(obj, pair.key, pair.value); | |
} | |
function decode<T>(token : Token, expectedType : TokenType, decoder : String -> T) : T { | |
var type = token.type; | |
var value = token.value; | |
if(type == expectedType) | |
return decoder(value); | |
else | |
throw('Can\'t parse $type as $expectedType'); | |
} | |
function decodeKeygroup(token : Token) : String { | |
return decode(token, TokenType.TkKeygroup, function(v) { | |
return v.substring(1, v.length - 1); | |
}); | |
} | |
function decodeString(token : Token) : String { | |
return decode(token, TokenType.TkString, function(v) { | |
try { | |
return unescape(v); | |
} catch(msg : String) { | |
InvalidToken(token); | |
return ""; | |
}; | |
}); | |
} | |
function decodeDatetime(token : Token) : Date { | |
return decode(token, TokenType.TkDatetime, function(v) { | |
var dateStr = ~/(T|Z)/.replace(v, ""); | |
return Date.fromString(dateStr); | |
}); | |
} | |
function decodeFloat(token : Token) : Float { | |
return decode(token, TokenType.TkFloat, function(v) { | |
return Std.parseFloat(v); | |
}); | |
} | |
function decodeInteger(token : Token) : Int { | |
return decode(token, TokenType.TkInteger, function(v) { | |
return Std.parseInt(v); | |
}); | |
} | |
function decodeBoolean(token : Token) : Bool { | |
return decode(token, TokenType.TkBoolean, function(v) { | |
return v == "true"; | |
}); | |
} | |
function decodeKey(token : Token) : String { | |
return decode(token, TokenType.TkKey, function(v) { return v; }); | |
} | |
function unescape(str : String) { | |
var pos = 0; | |
var buf = new haxe.Utf8(); | |
var len = Utf8.length(str); | |
while(pos < len) { | |
var c = Utf8.charCodeAt(str, pos); | |
// strip first and last quotation marks | |
if ((pos == 0 || pos == len-1) && c == "\"".code) { | |
pos++; | |
continue; | |
} | |
pos++; | |
if(c == "\\".code) { | |
c = Utf8.charCodeAt(str, pos); | |
pos++; | |
switch(c) { | |
case "r".code: buf.addChar("\r".code); | |
case "n".code: buf.addChar("\n".code); | |
case "t".code: buf.addChar("\t".code); | |
case "b".code: buf.addChar(8); | |
case "f".code: buf.addChar(12); | |
case "/".code, "\\".code, "\"".code: buf.addChar(c); | |
case "u".code: | |
var uc = Std.parseInt("0x" + Utf8.sub(str, pos, 4)); | |
buf.addChar(uc); | |
pos += 4; | |
default: | |
throw("Invalid Escape"); | |
} | |
} else { | |
buf.addChar(c); | |
} | |
} | |
return buf.toString(); | |
} | |
function tokenize(str : String) { | |
var tokens = new Array<Token>(); | |
var lineBreakPattern = ~/\r\n?|\n/g; | |
var lines = lineBreakPattern.split(str); | |
var a = ~/abc/; | |
var patterns = [ | |
{ type: TokenType.TkComment, ereg: ~/^#.*$/}, | |
{ type: TokenType.TkKeygroup, ereg: ~/^\[.+]/}, | |
{ type: TokenType.TkString, ereg: ~/^"((\\")|[^"])*"/}, | |
{ type: TokenType.TkAssignment, ereg: ~/^=/}, | |
{ type: TokenType.TkBBegin, ereg: ~/^\[/}, | |
{ type: TokenType.TkBEnd, ereg: ~/^]/}, | |
{ type: TokenType.TkComma, ereg: ~/^,/}, | |
{ type: TokenType.TkKey, ereg: ~/^\S+/}, | |
{ type: TokenType.TkDatetime, ereg: ~/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z/}, | |
{ type: TokenType.TkFloat, ereg: ~/^-?\d+\.\d+/}, | |
{ type: TokenType.TkInteger, ereg: ~/^-?\d+/}, | |
{ type: TokenType.TkBoolean, ereg: ~/^true|^false/}, | |
]; | |
for(lineNum in 0...lines.length) { | |
var line = lines[lineNum]; | |
var colNum = 0; | |
var tokenColNum = 0; | |
while(colNum < line.length) { | |
while(StringTools.isSpace(line, colNum)) { | |
colNum++; | |
} | |
if(colNum >= line.length) { | |
break; | |
} | |
var subline = line.substring(colNum); | |
var matched = false; | |
for(pattern in patterns) { | |
var type = pattern.type; | |
var ereg = pattern.ereg; | |
if(ereg.match(subline)) { | |
// TkKey has to be the first token of a line | |
if((type == TokenType.TkKeygroup || type == TokenType.TkKey) | |
&& tokenColNum != 0) { | |
continue; | |
} | |
if(type != TokenType.TkComment) { | |
tokens.push({ | |
type: type, | |
value: ereg.matched(0), | |
lineNum: lineNum, | |
colNum: colNum, | |
}); | |
tokenColNum++; | |
} | |
colNum += ereg.matchedPos().len; | |
matched = true; | |
break; | |
} | |
} | |
if(!matched) { | |
InvalidCharacter(line.charAt(colNum), lineNum, colNum); | |
} | |
} | |
} | |
return tokens; | |
} | |
function InvalidCharacter(char : String, lineNum : Int, colNum : Int) { | |
throw('Line $lineNum Character ${colNum+1}: ' + | |
'Invalid Character \'$char\', ' + | |
'Character Code ${char.charCodeAt(0)}'); | |
} | |
function InvalidToken(token : Token) { | |
throw('Line ${token.lineNum+1} Character ${token.colNum+1}: ' + | |
'Invalid Token \'${token.value}\'(${token.type})'); | |
} | |
/** Static shortcut method to parse toml String into Dynamic object. */ | |
public static function parseString(toml: String, defaultValue: Dynamic) | |
{ | |
return (new TomlParser()).parse(toml, defaultValue); | |
} | |
#if (neko || php || cpp) | |
/** Static shortcut method to read toml file and parse into Dynamic object. Available on Neko, PHP and CPP. */ | |
public static function parseFile(filename: String, ?defaultValue: Dynamic) | |
{ | |
return parseString(sys.io.File.getContent(filename), defaultValue); | |
} | |
#end | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment