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

audio: remove native pointer dependency on Source (#193)

* 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

* audio: remove native pointer dependency on Source

We can't store pointers to lua userdata in C. Lua may move the pointers at any time. So instead we must dereference the lua_ref to obtain the pointer at the point it is used, for example while mixing.

Fixes a problem where the engine can crash after some amount of time, depending wildly on game behaviors and memory usage. In many cases no crash may ever occur. I have a test locally (still WIP) that verifies this problem/fix.
parent 5217885f
......@@ -14,13 +14,12 @@
// any source which is playing must maintain a ref in lua, to avoid __gc.
typedef struct {
audio_Source* source;
int lua_ref;
} audioSourceWithRef;
} audioSourceByRef;
static int num_sources = 0;
static audioSourceWithRef* sources_playing = NULL;
static audioSourceByRef* sources_playing = NULL;
static float volume = 1.0;
#define CHANNELS 2
......@@ -31,29 +30,17 @@ static int16_t saturate(mixer_presaturate_t in) {
return cvt_presaturate_to_int16(in);
}
int audio_sources_nullify_refs(const audio_Source* source)
audio_Source* getSourcePtrFromRef(lua_State* L, audioSourceByRef ref)
{
int counted = 0;
if (!source) return 0;
// rather than crash, let's nullify any known references here,
// even if they're currently playing (they'll be cut to silence)
for(int i=0; i<num_sources; ++i)
{
audioSourceWithRef* srcref = &sources_playing[i];
if (srcref->source == source)
{
if (srcref->source->state != AUDIO_STOPPED)
++counted;
// do not free - the pointers in sources are lua user data
srcref->source = NULL;
srcref->lua_ref = LUA_NOREF;
}
}
return counted;
if (ref.lua_ref < 0)
return NULL;
lua_getglobal(L, "refs_audio_playing");
lua_pushinteger(L, ref.lua_ref);
lua_gettable(L, -2);
audio_Source* result = lua_touserdata(L, -1);
lua_pop(L,2);
return result;
}
// unrefs stopped sounds.
......@@ -65,31 +52,38 @@ void mixer_unref_stopped_sounds(lua_State* L)
{
if (sources_playing[i].lua_ref >= 0)
{
audio_Source* source = sources_playing[i].source;
audio_Source* source = getSourcePtrFromRef(L, sources_playing[i]);
if (!source || source->state == AUDIO_STOPPED)
{
lua_getglobal(L, "refs_audio_playing");
luaL_unref(L, -1, sources_playing[i].lua_ref);
// only unref is source is non-NULL -- assume a null source is already unref'd and that the
// stale part of our data is just sources_playing (to avoid unref'ing something that might already
// be re-using this ref ID)
if (source)
{
lua_getglobal(L, "refs_audio_playing");
luaL_unref(L, -1, sources_playing[i].lua_ref);
lua_pop(L,1);
}
sources_playing[i].lua_ref = LUA_REFNIL;
}
}
if (sources_playing[i].lua_ref < 0)
sources_playing[i].source = NULL;
}
}
void lutro_audio_stop_all(void)
void lutro_audio_stop_all(lua_State* L)
{
// Loop over audio sources
// no cleanup needed, __gc will handle it later after a call to mixer_unref_stopped_sounds()
for (int i = 0; i < num_sources; i++)
{
if (sources_playing[i].source)
sources_playing[i].source->state = AUDIO_STOPPED;
audio_Source* source = getSourcePtrFromRef(L, sources_playing[i]);
if (source)
source->state = AUDIO_STOPPED;
}
}
void mixer_render(int16_t *buffer)
void mixer_render(lua_State* L, int16_t *buffer)
{
static mixer_presaturate_t presaturateBuffer[AUDIO_FRAMES * CHANNELS];
......@@ -103,7 +97,8 @@ void mixer_render(int16_t *buffer)
// Loop over audio sources
for (int i = 0; i < num_sources; i++)
{
audio_Source* source = sources_playing[i].source;
audio_Source* source = getSourcePtrFromRef(L, sources_playing[i]);
if (!source)
continue;
......@@ -192,7 +187,11 @@ void mixer_render(int16_t *buffer)
}
}
}
continue;
}
// invalid source object - possibly asset loading failed, was invalid or it's been partially GC'd.
source->state = AUDIO_STOPPED;
}
// final saturation step - downsample.
......@@ -241,11 +240,10 @@ void lutro_audio_deinit()
// 1. assume luaState has been forcibly destroyed without its own cleanup.
// 2. run lua_close() and let it clean most of this up first.
lutro_audio_stop_all();
int counted = 0;
for (int i = 0; i < num_sources; i++)
{
if (sources_playing[i].source)
if (sources_playing[i].lua_ref >= 0)
++counted;
}
......@@ -268,7 +266,7 @@ static int find_empty_source_slot(audio_Source* self)
// it's possible a stopped source is still in the sources_playing list, since cleanup
// operations are deferred. This is OK and expected, just use the slot that's still assigned...
if (!sources_playing[i].source || sources_playing[i].source == self)
if (sources_playing[i].lua_ref < 0)
return i;
}
return -1;
......@@ -565,17 +563,6 @@ int source_gc(lua_State *L)
{
audio_Source* self = (audio_Source*)luaL_checkudata(L, 1, "Source");
// todo - add some info to help identify the offending leaker.
// (don't get carried away tho - this message is only really useful to lutro core devs since
// it indiciates a failure of our internal Lua/C glue)
int leaks = audio_sources_nullify_refs(self);
if (leaks)
{
fprintf(stderr, "source_gc: playing audio references were nullified.\n");
//assert(false);
}
luaL_unref(L, LUA_REGISTRYINDEX, self->lua_ref_sndta);
self->lua_ref_sndta = LUA_REFNIL;
......@@ -629,10 +616,11 @@ int audio_play(lua_State *L)
int slot = find_empty_source_slot(self);
if (slot < 0)
{
// TODO: This should be converted into a pooled allocator (a linked list of reusable blocks)
slot = num_sources++;
sources_playing = (audioSourceWithRef*)realloc(sources_playing, num_sources * sizeof(audioSourceWithRef));
sources_playing = (audioSourceByRef*)realloc(sources_playing, num_sources * sizeof(audioSourceByRef));
sources_playing[slot].lua_ref = LUA_REFNIL;
sources_playing[slot].source = NULL;
}
// assume that find_empty_source_slot with either return the current slot, or an empty one.
......@@ -640,13 +628,12 @@ int audio_play(lua_State *L)
{
lua_getglobal(L, "refs_audio_playing");
lua_pushvalue(L, 1); // push ref to Source parameter
sources_playing[slot].source = self;
sources_playing[slot].lua_ref = luaL_ref(L, -2);
}
else
{
// existing ref means it should be our same source already.
dbg_assert(sources_playing[slot].source == self);
dbg_assert(getSourcePtrFromRef(L, sources_playing[slot]) == self);
}
// for now sources always succeed in lutro.
......
......@@ -37,10 +37,11 @@ typedef struct
void lutro_audio_init(lua_State* L);
void lutro_audio_deinit();
void lutro_audio_stop_all();
void lutro_audio_stop_all(lua_State* L);
int lutro_audio_preload(lua_State *L);
void mixer_render(int16_t *buffer);
void lutro_mixer_render(int16_t* buffer);
void mixer_render(lua_State* L, int16_t *buffer);
void mixer_unref_stopped_sounds(lua_State* L);
int audio_newSource(lua_State *L);
......
......@@ -446,5 +446,7 @@ bool decWav_decode(dec_WavData *data, presaturate_buffer_desc *buffer, float vol
if (data->headc1.BitsPerSample == 16 && data->headc1.NumChannels == 1 && buffer->channels == 2) return _inl_decode_wav(data, bufsz, dst, 2, 1, 2, volume, loop);
if (data->headc1.BitsPerSample == 16 && data->headc1.NumChannels == 1 && buffer->channels == 1) return _inl_decode_wav(data, bufsz, dst, 2, 1, 1, volume, loop);
return 0;
// TODO: some unsupported format (32-bit float?)
// Return true to ensure the mixer stops and unrefs the sound data
return true;
}
......@@ -30,7 +30,7 @@ double frame_time = 0;
static void emit_audio(void)
{
mixer_render(audio_buffer);
lutro_mixer_render(audio_buffer);
audio_batch_cb(audio_buffer, AUDIO_FRAMES);
}
......
......@@ -317,13 +317,18 @@ void lutro_deinit()
lutro_live_deinit();
#endif
lutro_audio_stop_all();
lutro_audio_stop_all(L);
lua_gc(L, LUA_GCSTEP, 0);
lua_close(L);
lutro_audio_deinit();
lutro_filesystem_deinit();
}
void lutro_mixer_render(int16_t* buffer)
{
if (!L) return;
mixer_render(L, buffer);
}
int lutro_set_package_path(lua_State* L, const char* path)
......@@ -666,7 +671,7 @@ void lutro_reset()
if (lua_isfunction(L, -1))
{
lutro_audio_stop_all();
lutro_audio_stop_all(L);
if(lua_pcall(L, 0, 0, 0))
{
fprintf(stderr, "%s\n", lua_tostring(L, -1));
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment