Add assert() macro
This commit is contained in:
parent
85edde6d24
commit
ecddc0af92
131
Sources/armory/system/Assert.hx
Normal file
131
Sources/armory/system/Assert.hx
Normal file
|
@ -0,0 +1,131 @@
|
||||||
|
package armory.system;
|
||||||
|
|
||||||
|
import haxe.Exception;
|
||||||
|
import haxe.PosInfos;
|
||||||
|
import haxe.exceptions.PosException;
|
||||||
|
import haxe.macro.Context;
|
||||||
|
import haxe.macro.Expr;
|
||||||
|
|
||||||
|
using haxe.macro.ExprTools;
|
||||||
|
|
||||||
|
class Assert {
|
||||||
|
|
||||||
|
/**
|
||||||
|
Checks whether the given expression evaluates to true. If this is not
|
||||||
|
the case, an `ArmAssertionException` with additional information is
|
||||||
|
thrown.
|
||||||
|
|
||||||
|
The assert level describes the severity of the assertion. If the
|
||||||
|
severity is lower than the level stored in the `arm_assert_level` flag,
|
||||||
|
the assertion is omitted from the code so that it doesn't decrease the
|
||||||
|
runtime performance.
|
||||||
|
|
||||||
|
@param level The severity of this assertion.
|
||||||
|
@param condition The conditional expression to test.
|
||||||
|
@param message Optional message to display when the assertion fails.
|
||||||
|
|
||||||
|
@see `AssertLevel`
|
||||||
|
**/
|
||||||
|
macro public static function assert(level: ExprOf<AssertLevel>, condition: ExprOf<Bool>, message: String = ""): Expr {
|
||||||
|
final levelVal: AssertLevel = AssertLevel.fromExpr(level);
|
||||||
|
final assertThreshold = AssertLevel.fromString(Context.definedValue("arm_assert_level"));
|
||||||
|
|
||||||
|
if (levelVal < assertThreshold) {
|
||||||
|
return macro {};
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (levelVal) {
|
||||||
|
case Warning:
|
||||||
|
return macro {
|
||||||
|
if (!$condition) {
|
||||||
|
@:pos(condition.pos)
|
||||||
|
trace(@:privateAccess armory.system.Assert.ArmAssertionException.formatMessage($v{condition.toString()}, $v{message}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case Error:
|
||||||
|
return macro {
|
||||||
|
if (!$condition) {
|
||||||
|
#if arm_assert_quit kha.System.stop(); #end
|
||||||
|
|
||||||
|
@:pos(condition.pos)
|
||||||
|
@:privateAccess throwAssertionError($v{condition.toString()}, $v{message});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
throw new Exception('Unsupported assert level: $levelVal');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Helper function to prevent Haxe "bug" that actually throws an error
|
||||||
|
even when using `macro throw` (inlining this method also does not work).
|
||||||
|
**/
|
||||||
|
static function throwAssertionError(exprString: String, message: String, ?pos: PosInfos) {
|
||||||
|
throw new ArmAssertionException(exprString, message, pos);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Exception that is thrown when an assertion fails.
|
||||||
|
|
||||||
|
@see `Assert`
|
||||||
|
**/
|
||||||
|
class ArmAssertionException extends PosException {
|
||||||
|
|
||||||
|
/**
|
||||||
|
@param exprString The string representation of the failed assert condition.
|
||||||
|
@param message Custom error message, use an empty string to omit this.
|
||||||
|
**/
|
||||||
|
public inline function new(exprString: String, message: String, ?previous: Exception, ?pos: Null<PosInfos>) {
|
||||||
|
super('\n${formatMessage(exprString, message)}', previous, pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline function formatMessage(exprString: String, message: String): String {
|
||||||
|
final optMsg = message != "" ? '\n\tMessage: $message' : "";
|
||||||
|
|
||||||
|
return 'Failed assertion:$optMsg\n\tExpression: ($exprString)';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum abstract AssertLevel(Int) from Int to Int {
|
||||||
|
/**
|
||||||
|
Assertions with this severity don't throw exceptions and only print to
|
||||||
|
the console.
|
||||||
|
**/
|
||||||
|
var Warning: AssertLevel = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
Assertions with this severity throw an `ArmAssertionException` if they
|
||||||
|
fail, and optionally quit the game if the `arm_assert_quit` flag is set.
|
||||||
|
**/
|
||||||
|
var Error: AssertLevel = 1;
|
||||||
|
|
||||||
|
/**
|
||||||
|
Completely disable assertions. Don't use this level in `assert()` calls!
|
||||||
|
**/
|
||||||
|
var NoAssertions: AssertLevel = 2;
|
||||||
|
|
||||||
|
public static function fromExpr(e: ExprOf<AssertLevel>): AssertLevel {
|
||||||
|
switch (e.expr) {
|
||||||
|
case EConst(CIdent(v)): return fromString(v);
|
||||||
|
default: throw new Exception('Unsupported expression: $e');
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Converts a string into an `AssertLevel`, the string must be spelled
|
||||||
|
exactly as the assert level. `null` defaults to
|
||||||
|
`AssertLevel.NoAssertions`.
|
||||||
|
**/
|
||||||
|
public static function fromString(s: Null<String>): AssertLevel {
|
||||||
|
return switch (s) {
|
||||||
|
case "Warning": Warning;
|
||||||
|
case "Error": Error;
|
||||||
|
case "NoAssertions" | null: NoAssertions;
|
||||||
|
default: throw new Exception('Could not convert "$s" to AssertLevel');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@:op(A < B) static function lt(a: AssertLevel, b: AssertLevel): Bool;
|
||||||
|
@:op(A > B) static function gt(a: AssertLevel, b: AssertLevel): Bool;
|
||||||
|
}
|
|
@ -233,6 +233,14 @@ def init_properties():
|
||||||
bpy.types.World.arm_lod_gen_levels = IntProperty(name="Levels", description="Number of levels to generate", default=3, min=1)
|
bpy.types.World.arm_lod_gen_levels = IntProperty(name="Levels", description="Number of levels to generate", default=3, min=1)
|
||||||
bpy.types.World.arm_lod_gen_ratio = FloatProperty(name="Decimate Ratio", description="Decimate ratio", default=0.8)
|
bpy.types.World.arm_lod_gen_ratio = FloatProperty(name="Decimate Ratio", description="Decimate ratio", default=0.8)
|
||||||
bpy.types.World.arm_cache_build = BoolProperty(name="Cache Build", description="Cache build files to speed up compilation", default=True)
|
bpy.types.World.arm_cache_build = BoolProperty(name="Cache Build", description="Cache build files to speed up compilation", default=True)
|
||||||
|
bpy.types.World.arm_assert_level = EnumProperty(
|
||||||
|
items=[
|
||||||
|
('Warning', 'Warning', 'Warning level, warnings don\'t throw an ArmAssertException'),
|
||||||
|
('Error', 'Error', 'Error level. If assertions with this level fail, an ArmAssertException is thrown'),
|
||||||
|
('NoAssertions', 'No Assertions', 'Ignore all assertions'),
|
||||||
|
],
|
||||||
|
name="Assertion Level", description="Ignore all assertions below this level (assertions are turned off completely for published builds)", default='Warning', update=assets.invalidate_compiler_cache)
|
||||||
|
bpy.types.World.arm_assert_quit = BoolProperty(name="Quit On Assertion Fail", description="Whether to close the game when an 'Error' level assertion fails", default=False, update=assets.invalidate_compiler_cache)
|
||||||
bpy.types.World.arm_live_patch = BoolProperty(name="Live Patch", description="Live patching for Krom", default=False)
|
bpy.types.World.arm_live_patch = BoolProperty(name="Live Patch", description="Live patching for Krom", default=False)
|
||||||
bpy.types.World.arm_play_camera = EnumProperty(
|
bpy.types.World.arm_play_camera = EnumProperty(
|
||||||
items=[('Scene', 'Scene', 'Scene'),
|
items=[('Scene', 'Scene', 'Scene'),
|
||||||
|
|
|
@ -925,17 +925,19 @@ class ARM_PT_ProjectFlagsPanel(bpy.types.Panel):
|
||||||
layout.use_property_decorate = False
|
layout.use_property_decorate = False
|
||||||
wrd = bpy.data.worlds['Arm']
|
wrd = bpy.data.worlds['Arm']
|
||||||
|
|
||||||
col = layout.column(heading='Debug')
|
col = layout.column(heading='Debug', align=True)
|
||||||
col.prop(wrd, 'arm_verbose_output')
|
col.prop(wrd, 'arm_verbose_output')
|
||||||
col.prop(wrd, 'arm_cache_build')
|
col.prop(wrd, 'arm_cache_build')
|
||||||
|
col.prop(wrd, 'arm_assert_level')
|
||||||
|
col.prop(wrd, 'arm_assert_quit')
|
||||||
|
|
||||||
col = layout.column(heading='Runtime')
|
col = layout.column(heading='Runtime', align=True)
|
||||||
col.prop(wrd, 'arm_live_patch')
|
col.prop(wrd, 'arm_live_patch')
|
||||||
col.prop(wrd, 'arm_stream_scene')
|
col.prop(wrd, 'arm_stream_scene')
|
||||||
col.prop(wrd, 'arm_loadscreen')
|
col.prop(wrd, 'arm_loadscreen')
|
||||||
col.prop(wrd, 'arm_write_config')
|
col.prop(wrd, 'arm_write_config')
|
||||||
|
|
||||||
col = layout.column(heading='Renderer')
|
col = layout.column(heading='Renderer', align=True)
|
||||||
col.prop(wrd, 'arm_batch_meshes')
|
col.prop(wrd, 'arm_batch_meshes')
|
||||||
col.prop(wrd, 'arm_batch_materials')
|
col.prop(wrd, 'arm_batch_materials')
|
||||||
col.prop(wrd, 'arm_deinterleaved_buffers')
|
col.prop(wrd, 'arm_deinterleaved_buffers')
|
||||||
|
|
|
@ -159,7 +159,9 @@ project.addSources('Sources');
|
||||||
if wrd.arm_asset_compression:
|
if wrd.arm_asset_compression:
|
||||||
assets.add_khafile_def('arm_compress')
|
assets.add_khafile_def('arm_compress')
|
||||||
else:
|
else:
|
||||||
pass
|
assets.add_khafile_def(f'arm_assert_level={wrd.arm_assert_level}')
|
||||||
|
if wrd.arm_assert_quit:
|
||||||
|
assets.add_khafile_def('arm_assert_quit')
|
||||||
# khafile.write("""project.addParameter("--macro include('armory.trait')");\n""")
|
# khafile.write("""project.addParameter("--macro include('armory.trait')");\n""")
|
||||||
# khafile.write("""project.addParameter("--macro include('armory.trait.internal')");\n""")
|
# khafile.write("""project.addParameter("--macro include('armory.trait.internal')");\n""")
|
||||||
# if export_physics:
|
# if export_physics:
|
||||||
|
|
Loading…
Reference in a new issue