Unverified Commit a5ab4d4d authored by Jake Stine's avatar Jake Stine Committed by GitHub
Browse files

Add new build types: Debug, Tool, and Player (#188)

* Added new build types: Debug, Tool, and Player.

 Debug is -O0 compiled with all assertions enabled.
 Tool is -O1 compiled with debug assertions disabled entirely.
 Player is -O3 compiled with tool assertions disabled, and all ignorable assertions demoted to log-only.

Implementation of ignorable asserts is still partial, but as it stands this change is OK to go live (can add features later). On the todo list:
  - add popup to allow content creator to ignore the assert
  - add lua stacktrace information when lua context seems relevant

* GHActions updated to be aware of new player and tool build configs

* makefile: allow changing config from CLI without having to make clean

Also force rebuild of vcxproj target since dep checking is currently insufficient. it should be checking dirs of all globbed source file includes for modifications, but it's not. and this is a really fast step, no point in not running it for now

* Added lutro_errorf and lutro_alertf for logging errors and popup alerts, respectively.

 - Cleans up macros and keeps assertions clearly in the domain of Debug and Tooling builds.
 - removed play_assert and friends, which were both confusing terminology and not practical in design
parent 30b3bbc9
Pipeline #20337 passed with stages
in 6 minutes and 24 seconds
......@@ -10,25 +10,31 @@ defaults:
run:
shell: bash
# some jobs are currently running multiple config builds on the same worktree.
# it might be better to issue these as separate jobs or as a config matrix.
# debug builds are currently not built to keep the turnaround time on PRs shorter.
jobs:
build_linux:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- run: make -j4
- run: make -j4 config=tool
- run: git clean -f && make -j4 config=player
build_linux_jit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- run: make WANT_JIT=1 -j4
- run: make -j4 config=player WANT_JIT=1
build_linux_armv7_neon:
runs-on: ubuntu-latest
container: dockcross/linux-armv7
steps:
- uses: actions/checkout@v2
- run: make platform=unix-armv7-neon-hardfloat -j4
- run: make -j4 platform=unix-armv7-neon-hardfloat config=player
# build_linux_armv7_neon_jit:
# runs-on: ubuntu-latest
......@@ -41,23 +47,25 @@ jobs:
runs-on: macos-latest
steps:
- uses: actions/checkout@v2
- run: make platform=osx -j4
- run: make -j4 platform=osx config=tool
- run: git clean -f && make -j4 platform=osx config=player
build_osx_jit:
runs-on: macos-latest
steps:
- uses: actions/checkout@v2
- run: make WANT_JIT=1 platform=osx -j4
- run: make -j4 platform=osx config=player WANT_JIT=1
build_windows:
runs-on: windows-latest
steps:
- uses: actions/checkout@v2
- run: CC=gcc make platform=win -j4
- run: CC=gcc make -j4 platform=win config=tool
- run: git clean -f && CC=gcc make -j4 platform=win config=player
build_windows_jit:
runs-on: windows-latest
steps:
- uses: actions/checkout@v2
- run: CC=gcc make WANT_JIT=1 platform=win -j4
- run: CC=gcc make -j4 platform=win config=player WANT_JIT=1
HAVE_INOTIFY=0
HAVE_COMPOSITION=0
WANT_JIT=0
WANT_ZLIB=1
WANT_UNZIP=1
WANT_LUASOCKET=0
WANT_PHYSFS=0
#### CLI OPTIONS
# build configurations.
# config=debug full debug of player and tool portions of the engine (-O0)
# mainly for use in-house by Lutro engine developers.
# config=tool provides logging, assertion checking, special LUA APIs for content creators.
# this is the default config and is how releases of Lutro should be shipped to Love2D devs.
# config=player most assertions disabled or demoted to logs, special tooling APIs disabled.
# meant for packaging and shipping a Love2D game as a standalone playable 'finished product'.
#
# example:
# $ make config=[debug,tool,player])
# copy config -> LUTRO_CONFIG. 'config' is just a shorthand that we provide for the command line.
# it's better to keep these vars in slightly strong named vars for use through-out Makefile tho.
LUTRO_CONFIG ?= $(config)
LUTRO_CONFIG ?= tool
# WANTS use ?= syntax to allow things to be provided on command line
# NOTE: if you change these on the CLI then you MUST manually run clean!
# (this could be fixed with some clever make scripting, maybe later...)
WANT_JIT ?= 0
WANT_ZLIB ?= 1
WANT_UNZIP ?= 1
WANT_LUASOCKET ?= 0
WANT_PHYSFS ?= 0
#### END CLI OPTIONS
# setup some things that will be reassigned per-platform
HAVE_COMPOSITION = 0
HAVE_INOTIFY = 0
MMD := -MMD
ifeq ($(platform),)
......@@ -314,12 +341,47 @@ else
SHARED := -shared -static-libgcc -static-libstdc++ -s -Wl,--no-undefined
endif
ifeq ($(DEBUG), 1)
# platform agnostic defines/flags setup #
# include syms unconditionally. if a platform is size-sensitive or needs syms stripped for other reasons,
# then it is better to use the strip util (binutils) as a separate step explicitly. A typical process is
# that a symbolcated version of the binary is stored somewhere and then syms are stripped when the game is
# packaged for distribution. Bug reports from players can then pull syms from the storage when crashes are
# reported).
CFLAGS += -g
LUA_MYCFLAGS += -g
DEFINES_DEBUG += -DLUTRO_ENABLE_ERROR=1
DEFINES_DEBUG += -DLUTRO_ENABLE_ALERT=1
DEFINES_DEBUG += -DLUTRO_ENABLE_ASSERT_TOOL=1
DEFINES_DEBUG += -DLUTRO_ENABLE_ASSERT_DBG=1
DEFINES_TOOL += -DNDEBUG
DEFINES_TOOL += -DLUTRO_ENABLE_ERROR=1
DEFINES_TOOL += -DLUTRO_ENABLE_ALERT=1
DEFINES_TOOL += -DLUTRO_ENABLE_ASSERT_TOOL=1
DEFINES_TOOL += -DLUTRO_ENABLE_ASSERT_DBG=0
DEFINES_PLAYER += -DNDEBUG
DEFINES_TOOL += -DLUTRO_ENABLE_ERROR=1
DEFINES_TOOL += -DLUTRO_ENABLE_ALERT=0
DEFINES_TOOL += -DLUTRO_ENABLE_ASSERT_TOOL=0
DEFINES_TOOL += -DLUTRO_ENABLE_ASSERT_DBG=0
ifeq ($(LUTRO_CONFIG), debug)
DEFINES += $(DEFINES_DEBUG)
CFLAGS += -O0 -g
LUA_MYCFLAGS += -O0 -g -DLUA_USE_APICHECK
else
CFLAGS += -O3
LUA_MYCFLAGS += -O3
else ifeq ($(LUTRO_CONFIG), tool)
DEFINES += $(DEFINES_TOOL)
DEFINES += -DNDEBUG
CFLAGS += -O1 -g
LUA_MYCFLAGS += -O1 -g -DLUA_USE_APICHECK
else ifeq ($(LUTRO_CONFIG), player)
DEFINES += $(DEFINES_PLAYER)
DEFINES += -DNDEBUG
CFLAGS += -O3 -g
LUA_MYCFLAGS += -O3 -g
endif
CORE_DIR := .
......@@ -364,7 +426,10 @@ ifeq ($(platform), osx)
endif
endif
OBJS := $(addprefix obj/,$(OBJS))
INTDIR = obj/$(LUTRO_CONFIG)
OBJS := $(addprefix $(INTDIR)/,$(OBJS))
lutro_sources_file = msbuild/lutro_sources.props
all: $(TARGET)
......@@ -384,33 +449,58 @@ all: $(TARGET)
# to be dependency checks on the contents of each dir (gnu make supports this, just specify dirs as dependencies
# and any files added/removed/renamed within them will trigger a rule rebuild).
#
vcxproj: msbuild/lutro_sources.props
msbuild/lutro_sources.props: Makefile Makefile.common
@printf "msbuild/lutro_sources.props: Generating with %d sources, %d includes\n" $(words $(SOURCES_C)) $(words $(MSVC_SOURCES_H))
@> msbuild/lutro_sources.props echo '<?xml version="1.0" encoding="utf-8"?>'
@>> msbuild/lutro_sources.props echo '<!-- AUTO_GENERATED FILE - generated by 'make vcxproj' -->'
@>> msbuild/lutro_sources.props echo '<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">'
@>> msbuild/lutro_sources.props echo ' <ItemGroup>'
@>> msbuild/lutro_sources.props printf " <ClCompile Include=\"../%s\" />\n" $(SOURCES_C)
@>> msbuild/lutro_sources.props echo ' </ItemGroup>'
.PHONY: vcxproj
vcxproj: $(lutro_sources_file)
$(lutro_sources_file): FORCE # Makefile Makefile.common
@printf "$(lutro_sources_file): Generating with %d sources, %d includes\n" $(words $(SOURCES_C)) $(words $(MSVC_SOURCES_H))
@> $(lutro_sources_file) echo '<?xml version="1.0" encoding="utf-8"?>'
@>> $(lutro_sources_file) echo '<!-- AUTO_GENERATED FILE - generated by 'make vcxproj' -->'
@>> $(lutro_sources_file) echo '<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">'
@>> $(lutro_sources_file) echo ' <ItemGroup>'
@>> $(lutro_sources_file) printf " <ClCompile Include=\"../%s\" />\n" $(SOURCES_C) $(SOURCES_CXX)
@>> $(lutro_sources_file) echo ' </ItemGroup>'
@[[ -z "$(MSVC_SOURCES_H)" ]] || \
>> msbuild/lutro_sources.props echo ' <ItemGroup>' && \
>> msbuild/lutro_sources.props printf " <ClInclude Include=\"../%s\" />\n" $(MSVC_SOURCES_H) && \
>> msbuild/lutro_sources.props echo ' </ItemGroup>'
@>> msbuild/lutro_sources.props echo ' <ItemDefinitionGroup>'
@>> msbuild/lutro_sources.props echo ' <ClCompile>'
@>> msbuild/lutro_sources.props printf " <AdditionalIncludeDirectories>%%(AdditionalIncludeDirectories);../%s</AdditionalIncludeDirectories>\n" $(subst -I,,$(filter -I%,$(CFLAGS)))
@>> msbuild/lutro_sources.props echo ' </ClCompile>'
@>> msbuild/lutro_sources.props echo ' </ItemDefinitionGroup>'
@>> msbuild/lutro_sources.props echo '</Project>'
>> $(lutro_sources_file) echo ' <ItemGroup>' && \
>> $(lutro_sources_file) printf " <ClInclude Include=\"../%s\" />\n" $(MSVC_SOURCES_H) && \
>> $(lutro_sources_file) echo ' </ItemGroup>'
@>> $(lutro_sources_file) echo ' <ItemDefinitionGroup>'
@>> $(lutro_sources_file) echo ' <ClCompile>'
@>> $(lutro_sources_file) printf " <AdditionalIncludeDirectories>%%(AdditionalIncludeDirectories);../%s</AdditionalIncludeDirectories>\n" $(subst -I,,$(filter -I%,$(CFLAGS)))
@>> $(lutro_sources_file) echo ' </ClCompile>'
@>> $(lutro_sources_file) echo ' </ItemDefinitionGroup>'
@>> $(lutro_sources_file) printf " <ItemDefinitionGroup Condition=\"'%s'=='Debug'\">\n" "$$""(Configuration)"
@>> $(lutro_sources_file) echo ' <ClCompile>'
@>> $(lutro_sources_file) printf " <PreprocessorDefinitions>%%(PreprocessorDefinitions);%s</PreprocessorDefinitions>\n" $(subst -D,,$(filter -D%,$(DEFINES_DEBUG)))
@>> $(lutro_sources_file) echo ' </ClCompile>'
@>> $(lutro_sources_file) echo ' </ItemDefinitionGroup>'
@>> $(lutro_sources_file) printf " <ItemDefinitionGroup Condition=\"'%s'=='Tool'\">\n" "$$""(Configuration)"
@>> $(lutro_sources_file) echo ' <ClCompile>'
@>> $(lutro_sources_file) printf " <PreprocessorDefinitions>%%(PreprocessorDefinitions);%s</PreprocessorDefinitions>\n" $(subst -D,,$(filter -D%,$(DEFINES_TOOL)))
@>> $(lutro_sources_file) echo ' </ClCompile>'
@>> $(lutro_sources_file) echo ' </ItemDefinitionGroup>'
@>> $(lutro_sources_file) printf " <ItemDefinitionGroup Condition=\"'%s'=='Player'\">\n" "$$""(Configuration)"
@>> $(lutro_sources_file) echo ' <ClCompile>'
@>> $(lutro_sources_file) printf " <PreprocessorDefinitions>%%(PreprocessorDefinitions);%s</PreprocessorDefinitions>\n" $(subst -D,,$(filter -D%,$(DEFINES_PLAYER)))
@>> $(lutro_sources_file) echo ' </ClCompile>'
@>> $(lutro_sources_file) echo ' </ItemDefinitionGroup>'
@>> $(lutro_sources_file) echo '</Project>'
@touch msbuild/lutro.vcxproj
ifneq ($(MMD),)
-include $(OBJS:.o=.d)
endif
$(TARGET): $(OBJS) $(LUALIB)
# hard-link the generared binary into the parent dir. this is forced since its fast and
# the current target may be out-of-date if switching between two existing config builds.
$(TARGET): $(INTDIR)/$(TARGET) FORCE
ln -f $(INTDIR)/$(TARGET) $(TARGET)
$(INTDIR)/$(TARGET): $(OBJS) $(LUALIB)
ifeq ($(STATIC_LINKING), 1)
$(AR) rcs $@ $(OBJS) $(LUADIR)/*.o
else
......@@ -422,16 +512,20 @@ deps/lua/src/liblua.a:
deps/luajit/src/libluajit.a:
$(MAKE) -C deps/luajit/src CC="$(CC)" CXX="$(CXX)" BUILDMODE=static CFLAGS="$(LUA_MYCFLAGS) -w $(fpic)" Q= LDFLAGS="$(LDFLAGS) $(fpic)" libluajit.a
obj/%.o: %.c
$(INTDIR)/%.o: %.c
@mkdir -p $(dir $@)
$(CC) $(CFLAGS) $(GVFLAGS) $(MMD) -c -o $@ $<
clean:
-make -C $(LUADIR) clean
-rm -f $(OBJS) $(TARGET)
-rm -f $(OBJS) $(TARGET) $(INTDIR)/$(TARGET)
if [ -d "obj" ]; then rm -rf obj; fi
test: all
retroarch --verbose -L lutro_libretro.so test/main.lua
FORCE:
.PHONY: clean
.PHONY: FORCE
......@@ -8,7 +8,6 @@
#include <file/file_path.h>
#include <audio/conversion/float_to_s16.h>
#include <math.h>
#include <assert.h>
#include <errno.h>
/* TODO/FIXME - no sound on big-endian */
......@@ -179,8 +178,8 @@ void mixer_render(int16_t *buffer)
}
}
assert(source->sndpos <= sndta->numSamples);
assert(total_mixed <= AUDIO_FRAMES);
dbg_assume(source->sndpos <= sndta->numSamples);
dbg_assume(total_mixed <= AUDIO_FRAMES);
if (source->sndpos == sndta->numSamples)
{
......@@ -617,7 +616,12 @@ int audio_play(lua_State *L)
return 0;
}
assert(self->state == AUDIO_STOPPED);
if (self->state != AUDIO_STOPPED)
{
lutro_alertf("Invalid audio state value=%d", self->state);
return 0;
}
self->state = AUDIO_PLAYING;
// add a ref to our playing audio registry. this blocks __gc until the ref(s) are removed.
......@@ -642,7 +646,7 @@ int audio_play(lua_State *L)
else
{
// existing ref means it should be our same source already.
assert(sources_playing[slot].source == self);
dbg_assert(sources_playing[slot].source == self);
}
// for now sources always succeed in lutro.
......
......@@ -3,8 +3,8 @@
#include <audio/conversion/float_to_s16.h>
#include <errno.h>
#include <string.h>
#include <assert.h>
#include "lutro_assert.h"
#include "decoder.h"
#include "audio.h"
......@@ -36,7 +36,15 @@ bool decOgg_init(dec_OggData *data, const char *filename)
data->info = NULL;
if (ov_fopen(filename, &data->vf) < 0)
{
fprintf(stderr, "Failed to open vorbis file: %s\n", filename);
// only print for file not found errors.
// caller's intent might be to silently try opening "optional" files until one succeeeds.
if (errno == ENOENT)
{
fprintf(stderr, "vorbis file not found: %s\n", filename);
return false;
}
lutro_errorf("vorbis: Failed to open file: %s", filename, strerror(errno));
return false;
}
......@@ -46,7 +54,7 @@ bool decOgg_init(dec_OggData *data, const char *filename)
data->info = (vorbis_info*)ov_info(&data->vf, 0);
if (!data->info)
{
fprintf(stderr, "couldn't get info for file\n");
lutro_errorf("vorbis: couldn't get info for file: %s", filename);
return false;
}
......@@ -55,13 +63,13 @@ bool decOgg_init(dec_OggData *data, const char *filename)
if (data->info->channels != 1 && data->info->channels != 2)
{
fprintf(stderr, "unsupported number of channels\n");
lutro_errorf("vorbis: unsupported number of channels");
return false;
}
if (data->info->rate != 44100)
{
fprintf(stderr, "unsupported sample rate\n");
lutro_errorf("vorbis: unsupported sample rate");
return false;
}
......@@ -113,7 +121,7 @@ bool decOgg_decode(dec_OggData *data, presaturate_buffer_desc *buffer, float vol
if (ret < 0)
{
fprintf(stderr, "Vorbis decoding failed with: %jd\n", ret);
lutro_errorf("Vorbis decoding failed with: %jd", ret);
return true;
}
......@@ -204,18 +212,21 @@ bool decWav_init(dec_WavData *data, const char *filename)
{
int err = errno;
// if file is missing do not report error.
// only print for file not found errors.
// caller's intent might be to silently try opening "optional" files until one succeeeds.
if (errno == ENOENT)
{
fprintf(stderr, "wavfile not found '%s'\n", filename);
return 0;
}
fprintf(stderr, "Failed to open wavfile '%s': %s\n", filename, strerror(err));
lutro_errorf("Failed to open wavfile '%s': %s", filename, strerror(err));
return 0;
}
if (fread(&data->head, WAV_HEADER_SIZE, 1, fp) == 0)
{
fprintf(stderr, "%s is not a valid wav file or is truncated.\n", filename);
lutro_errorf("%s is not a valid wav file or is truncated.", filename);
fclose(fp);
return 0;
}
......@@ -249,7 +260,7 @@ bool decWav_seek(dec_WavData *data, intmax_t samplepos)
// seek pos matches
if (data->pos == bytepos)
{
assert(ftell(data->fp) == WAV_HEADER_SIZE + bytepos);
tool_assert(ftell(data->fp) == WAV_HEADER_SIZE + bytepos);
return 1;
}
......@@ -317,7 +328,9 @@ static __always_inline bool _inl_decode_wav(dec_WavData *data, intmax_t bufsz, m
if (!readResult)
{
assert(data->pos == ftell(data->fp) - WAV_HEADER_SIZE);
dbg_assertf(data->pos == ftell(data->fp) - WAV_HEADER_SIZE, "numSamples=%jd dataPos=%jd and ftell=%jd",
numSamples, (intmax_t)data->pos, ftell(data->fp)
);
if (!loop)
{
......@@ -362,7 +375,9 @@ static __always_inline bool _inl_decode_wav(dec_WavData *data, intmax_t bufsz, m
}
}
assert(data->pos == ftell(data->fp) - WAV_HEADER_SIZE);
dbg_assertf(data->pos == ftell(data->fp) - WAV_HEADER_SIZE, "numSamples=%jd dataPos=%jd and ftell=%jd",
numSamples, (intmax_t)data->pos, ftell(data->fp)
);
return 0;
}
......
......@@ -116,6 +116,32 @@ static void dumpstack( lua_State* L )
}
#endif
int _lutro_assertf_internal(int ignorable, const char *fmt, ...)
{
va_list argptr;
va_start(argptr, fmt);
vfprintf(stderr, fmt, argptr);
va_end(argptr);
fflush(NULL);
// tips: the fmt input should always be in the predefined format of:
// FILE(LINE): assertion `cond` failed.
// example:
// lutro.cpp(444): assertion `x > 0` failed.
//
// We can use this knowledge to parse the file and line positions and perform additional clever filtering
// or log prep/routing.
if (ignorable)
{
// TODO : suspend the core, show up some user dialog via libretro api?
// (it could even have lots of info, like lua stacktrace... )
// return 0 if ignored.
}
return 1;
}
#define LEVELS1 12 /* size of the first part of the stack */
#define LEVELS2 10 /* size of the second part of the stack */
......
......@@ -5,6 +5,8 @@
#include <stdbool.h>
#include <libretro.h>
#include "lutro_assert.h"
#ifndef PATH_MAX_LENGTH
#define PATH_MAX_LENGTH 4096
#endif
......
......@@ -14,21 +14,28 @@ EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|x64 = Debug|x64
Release|x64 = Release|x64
Player|x64 = Player|x64
Tool|x64 = Tool|x64
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{18CEB57F-9D09-4FAB-AEEE-08B094055423}.Debug|x64.ActiveCfg = Debug|x64
{18CEB57F-9D09-4FAB-AEEE-08B094055423}.Debug|x64.Build.0 = Debug|x64
{18CEB57F-9D09-4FAB-AEEE-08B094055423}.Release|x64.ActiveCfg = Release|x64
{18CEB57F-9D09-4FAB-AEEE-08B094055423}.Release|x64.Build.0 = Release|x64
{18CEB57F-9D09-4FAB-AEEE-08B094055423}.Player|x64.ActiveCfg = Player|x64
{18CEB57F-9D09-4FAB-AEEE-08B094055423}.Player|x64.Build.0 = Player|x64
{18CEB57F-9D09-4FAB-AEEE-08B094055423}.Tool|x64.ActiveCfg = Tool|x64
{18CEB57F-9D09-4FAB-AEEE-08B094055423}.Tool|x64.Build.0 = Tool|x64
{A5743DF4-3D23-4D9F-A01E-6903F364601B}.Debug|x64.ActiveCfg = Debug|x64
{A5743DF4-3D23-4D9F-A01E-6903F364601B}.Debug|x64.Build.0 = Debug|x64
{A5743DF4-3D23-4D9F-A01E-6903F364601B}.Release|x64.ActiveCfg = Release|x64
{A5743DF4-3D23-4D9F-A01E-6903F364601B}.Release|x64.Build.0 = Release|x64
{A5743DF4-3D23-4D9F-A01E-6903F364601B}.Player|x64.ActiveCfg = Release|x64
{A5743DF4-3D23-4D9F-A01E-6903F364601B}.Player|x64.Build.0 = Release|x64
{A5743DF4-3D23-4D9F-A01E-6903F364601B}.Tool|x64.ActiveCfg = Debug|x64
{A5743DF4-3D23-4D9F-A01E-6903F364601B}.Tool|x64.Build.0 = Debug|x64
{B4A07901-6615-4460-B907-5F0A2838AC34}.Debug|x64.ActiveCfg = Debug|x64
{B4A07901-6615-4460-B907-5F0A2838AC34}.Debug|x64.Build.0 = Debug|x64
{B4A07901-6615-4460-B907-5F0A2838AC34}.Release|x64.ActiveCfg = Release|x64
{B4A07901-6615-4460-B907-5F0A2838AC34}.Release|x64.Build.0 = Release|x64
{B4A07901-6615-4460-B907-5F0A2838AC34}.Player|x64.ActiveCfg = Release|x64
{B4A07901-6615-4460-B907-5F0A2838AC34}.Player|x64.Build.0 = Release|x64
{B4A07901-6615-4460-B907-5F0A2838AC34}.Tool|x64.ActiveCfg = Debug|x64
{B4A07901-6615-4460-B907-5F0A2838AC34}.Tool|x64.Build.0 = Debug|x64
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
......
#ifndef LUTRO_ASSERT_H
#define LUTRO_ASSERT_H
// setup some sane defaults for assertions - these should ideally be specified explicitly by the build system.
// Disabling Errors is strongly discouraged. This option only exists for build system troubleshooting.
#ifndef LUTRO_ENABLE_ERROR
#define LUTRO_ENABLE_ERROR 1
#endif
// Recommended: Disable alerts on Player Builds.
#ifndef LUTRO_ENABLE_ALERT
#define LUTRO_ENABLE_ALERT 1
#endif
#ifndef LUTRO_ENABLE_TOOL_ASSERT
#define LUTRO_ENABLE_TOOL_ASSERT 1
#endif
#if !defined(LUTRO_ENABLE_DBG_ASSERT)
# if defined(NDEBUG)
# define LUTRO_ENABLE_DBG_ASSERT 0
# else
# define LUTRO_ENABLE_DBG_ASSERT 1
# endif
#endif
#if !defined(__clang__)
# if defined(_MSC_VER)
# define __builtin_assume(cond) __assume(cond)
# else
// GCC has __builtin_unreachable, but it's not really a 1:1 match for __builtin_assume since the assume
// directive has special rules about not evaluating conditions that might have side effects.
# define __builtin_assume(cond) ((void)(0 && (cond)))
# endif
#endif
typedef enum
{
AssertUnrecoverable,
AssertIgnorable,
AssertErrorOnly
} AssertionType;
// Some macro engineering notes:
// - the disabled versions of assertions still include the cond, gated behind `0 &&` which makes the cond unreachable.
// this is done to enforce syntax checking of the contents of the assertion conditional even in release builds.
extern int _lutro_assertf_internal(int ignorable, const char *fmt, ...);
#define _base_hard_error() ((void)( ((_lutro_assertf_internal(AssertUnrecoverable, __FILE__ "(%d):unrecoverable error " "\n", __LINE__ ),1) && (abort(), 0))))
#define _base_hard_errorf(msg, ...) ((void)( ((_lutro_assertf_internal(AssertUnrecoverable, __FILE__ "(%d):unrecoverable error " msg "\n", __LINE__, ## __VA_ARGS__),1) && (abort(), 0))))
#define _base_soft_alert() ((void)( ((_lutro_assertf_internal(AssertIgnorable, __FILE__ "(%d):alert " "\n", __LINE__ ) ) && (abort(), 0))))
#define _base_soft_alertf(msg, ...) ((void)( ((_lutro_assertf_internal(AssertIgnorable, __FILE__ "(%d):alert " msg "\n", __LINE__, ## __VA_ARGS__) ) && (abort(), 0))))
#define _base_soft_error() ((void)( ((_lutro_assertf_internal(AssertErrorOnly, __FILE__ "(%d):error " "\n", __LINE__ ),0) )))
#define _base_soft_errorf(msg, ...) ((void)( ((_lutro_assertf_internal(AssertErrorOnly, __FILE__ "(%d):error " msg "\n", __LINE__, ## __VA_ARGS__),0) )))
#define _base_cond_error(cond) ((void)((cond) || ((_lutro_assertf_internal(AssertErrorOnly, __FILE__ "(%d):assertion `%s` failed. " "\n", __LINE__, #cond ),0) )))
#define _base_cond_errorf(cond, msg, ...) ((void)((cond) || ((_lutro_assertf_internal(AssertErrorOnly, __FILE__ "(%d):assertion `%s` failed. " msg "\n", __LINE__, #cond , ## __VA_ARGS__),0) )))
#define _base_hard_assert(cond) ((void)((cond) || ((_lutro_assertf_internal(AssertUnrecoverable, __FILE__ "(%d):assertion `%s` failed. " "\n", __LINE__, #cond ),1) && (abort(), 0))))
#define _base_hard_assertf(cond, msg, ...) ((void)((cond) || ((_lutro_assertf_internal(AssertUnrecoverable, __FILE__ "(%d):assertion `%s` failed. " msg "\n", __LINE__, #cond , ## __VA_ARGS__),1) && (abort(), 0))))
#define _base_soft_assert(cond) ((void)((cond) || ((_lutro_assertf_internal(AssertIgnorable, __FILE__ "(%d):assertion `%s` failed. " "\n", __LINE__, #cond ) ) && (abort(), 0))))
#define _base_soft_assertf(cond, msg, ...) ((void)((cond) || ((_lutro_assertf_internal(AssertIgnorable, __FILE__ "(%d):assertion `%s` failed. " msg "\n", __LINE__, #cond , ## __VA_ARGS__) ) && (abort(), 0))))
// lutro error reporting. Errors come in two forms:
// - error / alert /fail (no condition is provided in the paramters)
// - assertions (a condition is provided in the parameters)
//
// Assertions are meants to be compiled out in Release/Player builds. These constructs are generally only useful
// for internal debugging and some tooling debugging uses. They are meant to be used when the condition check
// itself would cause degredation of performance unnecessarily for the user (Player builds).
//
// Errors are unconditional, in the sense that the condition must be specified normally by the programmer.
// The conditional behavior does not change depending on build type. Errors are useful in game development as
// the majority of "errors" are non-fatal situations that impact gameplay in varying degrees, ranging from
// simple missing visual/audio fx to perhaps buggy gameplay. It sbetter to just keep playing than to crash.
//
// Summary of Macros:
// - lutro_errorf is a logging construct only. It does not stop play in any build. It intentionally does not
// include built-in conditional checks because it is expected the condition is always performed and that
// execution continues past the error.
//
// - lutro_alertf stops execution and issues a popup to the developer in Tool/Debug builds, which can be ignored
// or turned into a crash report. In player builds it logs and (optionally) may record telemetry info to
// allow debugging and fixing "misbehaves" later on.
//
// - lutro_failf stops execution in ALL builds. It issues a popup to the developer in Tool/Debug builds, and
// goes straight to a crash dump report in player builds.
//
#if LUTRO_ENABLE_ERROR
# define lutro_error() _base_soft_error ()
# define lutro_errorf(msg, ...) _base_soft_errorf (msg, ## __VA_ARGS__)
# define lutro_fail() _base_hard_error ()
# define lutro_failf(msg, ...) _base_hard_errorf (msg, ## __VA_ARGS__)
#else
# define lutro_error() ((void)0)
# define lutro_errorf(msg, ...) ((void)0)
# define lutro_fail() (__builtin_assume(0))
# define lutro_failf(msg, ...) (__builtin_assume(0))
#endif
#if LUTRO_ENABLE_ALERT
# define lutro_alert() _base_soft_alert ()
# define lutro_alertf(msg, ...) _base_soft_alertf ( msg, ## __VA_ARGS__)
#elif LUTRO_ENABLE_ERROR
# define lutro_alert() _base_soft_error ()
# define lutro_alertf(msg, ...) _base_soft_errorf ( msg, ## __VA_ARGS__)
#else
# define lutro_alert() ((void)0))
# define lutro_alertf(msg, ...) ((void)0))
#endif
// tool_assert is meant to be conditionally compiled out on player builds. The main justifications to use tool_assert:
// - for performance reasons, eg. if a play_errorf is known to be a performance issue
// - to avoid surfacing errors that only make sense from the context of content creation, or simply don't
// make sense in the context of the player
//
// tool_assume is a "hardened" version of assert, and is not ignorable.
#if LUTRO_ENABLE_TOOL_ASSERT
# define tool_assert(cond) _base_soft_assert (cond)
# define tool_assertf(cond, msg, ...) _base_soft_assertf(cond, msg, ## __VA_ARGS__)
# define tool_assume(cond) _base_hard_assert (cond)
# define tool_assumef(cond, msg, ...) _base_hard_assertf(cond, msg, ## __VA_ARGS__)
#else
# define tool_assert(cond) (__builtin_assume(cond))
# define tool_assertf(cond, msg, ...) (__builtin_assume(cond))
# define tool_assume(cond) (__builtin_assume(cond))
# define tool_assumef(cond, msg, ...) (__builtin_assume(cond))
#endif
// dbg_assert is enabled only in debug builds, and is for use only in performance-critical code paths where