diff --git a/src/libprojectM/MilkdropPreset/CustomShape.hpp b/src/libprojectM/MilkdropPreset/CustomShape.hpp index 5090b37eb7..2456ad8ce9 100644 --- a/src/libprojectM/MilkdropPreset/CustomShape.hpp +++ b/src/libprojectM/MilkdropPreset/CustomShape.hpp @@ -35,7 +35,7 @@ class CustomShape /** * @brief Compiles all code blocks and runs the init expression. - * @throws MilkdropCompileException Thrown if one of the code blocks couldn't be compiled. + * If one of the code blocks can't be compiled, a warning is logged and the code is skipped, like in Milkdrop. */ void CompileCodeAndRunInitExpressions(); diff --git a/src/libprojectM/MilkdropPreset/CustomWaveform.hpp b/src/libprojectM/MilkdropPreset/CustomWaveform.hpp index 7c4c3df1a2..e563bdb544 100644 --- a/src/libprojectM/MilkdropPreset/CustomWaveform.hpp +++ b/src/libprojectM/MilkdropPreset/CustomWaveform.hpp @@ -33,7 +33,7 @@ class CustomWaveform /** * @brief Compiles all code blocks and runs the init expression. - * @throws MilkdropCompileException Thrown if one of the code blocks couldn't be compiled. + * If one of the code blocks can't be compiled, a warning is logged and the code is skipped, like in Milkdrop. * @param presetPerFrameContext The per-frame context to retrieve the init Q vars from. */ void CompileCodeAndRunInitExpressions(const PerFrameContext& presetPerFrameContext); diff --git a/src/libprojectM/MilkdropPreset/PerFrameContext.cpp b/src/libprojectM/MilkdropPreset/PerFrameContext.cpp index b2063825be..d8ecc21f34 100644 --- a/src/libprojectM/MilkdropPreset/PerFrameContext.cpp +++ b/src/libprojectM/MilkdropPreset/PerFrameContext.cpp @@ -139,7 +139,14 @@ void PerFrameContext::EvaluateInitCode(PresetState& state) error = "[PerFrameContext] Could not compile per-frame init code."; } LOG_DEBUG("[PerFrameContext] Failed per-frame INIT code:\n" + state.perFrameInitCode); - throw MilkdropCompileException(error); + // Like Milkdrop, only this code block is disabled, the preset is still used. + LOG_WARN(error); + for (int q = 0; q < QVarCount; q++) + { + q_values_after_init_code[q] = 0; + state.frameQVariables[q] = 0; + } + return; } projectm_eval_code_execute(initCode); @@ -260,7 +267,9 @@ void PerFrameContext::CompilePerFrameCode(const std::string& perFrameCode) error = "[PerFrameContext] Could not compile per-frame code."; } LOG_DEBUG("[PerFrameContext] Failed per-frame code:\n" + perFrameCode); - throw MilkdropCompileException(error); + // Like Milkdrop, only this code block is disabled, the preset is still used. + LOG_WARN(error); + return; } } diff --git a/src/libprojectM/MilkdropPreset/PerFrameContext.hpp b/src/libprojectM/MilkdropPreset/PerFrameContext.hpp index aca786ba88..17f36dce85 100644 --- a/src/libprojectM/MilkdropPreset/PerFrameContext.hpp +++ b/src/libprojectM/MilkdropPreset/PerFrameContext.hpp @@ -46,14 +46,14 @@ class PerFrameContext /** * @brief Compiles and runs the preset init code. - * @throws MilkdropCompileException Thrown if the per-frame init code couldn't be compiled. + * If the per-frame init code can't be compiled, a warning is logged and the code is skipped, like in Milkdrop. * @param perFrameInitCode The init code. */ void EvaluateInitCode(PresetState& state); /** * @brief Compiles the per-frame code and stores the code handle in the class. - * @throws MilkdropCompileException Thrown if the per-frame code couldn't be compiled. + * If the per-frame code can't be compiled, a warning is logged and the code is skipped, like in Milkdrop. * @param perFrameCode The code to compile. */ void CompilePerFrameCode(const std::string& perFrameCode); diff --git a/src/libprojectM/MilkdropPreset/PerPixelContext.cpp b/src/libprojectM/MilkdropPreset/PerPixelContext.cpp index 2942a7f2fe..16bab2d2cb 100644 --- a/src/libprojectM/MilkdropPreset/PerPixelContext.cpp +++ b/src/libprojectM/MilkdropPreset/PerPixelContext.cpp @@ -124,7 +124,9 @@ void PerPixelContext::CompilePerPixelCode(const std::string& perPixelCode) error = "[PerPixelContext] Could not compile per-pixel code."; } LOG_DEBUG("[PerPixelContext] Failed per-pixel code:\n" + perPixelCode); - throw MilkdropCompileException(error); + // Like Milkdrop, only this code block is disabled, the preset is still used. + LOG_WARN(error); + return; } } diff --git a/src/libprojectM/MilkdropPreset/PerPixelContext.hpp b/src/libprojectM/MilkdropPreset/PerPixelContext.hpp index e20acadb21..2c9e167bfc 100644 --- a/src/libprojectM/MilkdropPreset/PerPixelContext.hpp +++ b/src/libprojectM/MilkdropPreset/PerPixelContext.hpp @@ -48,7 +48,7 @@ class PerPixelContext /** * @brief Compiles the per-pixel code and stores the code handle in the class. - * @throws MilkdropCompileException Thrown if the per-pixel code couldn't be compiled. + * If the per-pixel code can't be compiled, a warning is logged and the code is skipped, like in Milkdrop. * @param perPixelCode The code to compile. */ void CompilePerPixelCode(const std::string& perPixelCode); diff --git a/src/libprojectM/MilkdropPreset/ShapePerFrameContext.cpp b/src/libprojectM/MilkdropPreset/ShapePerFrameContext.cpp index e31f5332db..e86c5e9386 100644 --- a/src/libprojectM/MilkdropPreset/ShapePerFrameContext.cpp +++ b/src/libprojectM/MilkdropPreset/ShapePerFrameContext.cpp @@ -160,7 +160,13 @@ void ShapePerFrameContext::EvaluateInitCode(const std::string& perFrameInitCode, error = "[ShapePerFrameContext] Could not compile custom shape " + std::to_string(shape.m_index) + " per-frame init code."; } LOG_DEBUG("[ShapePerFrameContext] Failed custom shape per-frame INIT code:\n" + perFrameInitCode); - throw MilkdropCompileException(error); + // Like Milkdrop, only this code block is disabled, the preset is still used. + LOG_WARN(error); + for (auto* tVar : t_vars) + { + *tVar = 0; + } + return; } projectm_eval_code_execute(initCode); @@ -195,7 +201,9 @@ void ShapePerFrameContext::CompilePerFrameCode(const std::string& perFrameCode, error = "[ShapePerFrameContext] Could not compile custom shape " + std::to_string(shape.m_index) + " per-frame code."; } LOG_DEBUG("[ShapePerFrameContext] Failed custom shape per-frame code:\n" + perFrameCode); - throw MilkdropCompileException(error); + // Like Milkdrop, only this code block is disabled, the preset is still used. + LOG_WARN(error); + return; } } diff --git a/src/libprojectM/MilkdropPreset/ShapePerFrameContext.hpp b/src/libprojectM/MilkdropPreset/ShapePerFrameContext.hpp index f52095ebe7..e047f8ef31 100644 --- a/src/libprojectM/MilkdropPreset/ShapePerFrameContext.hpp +++ b/src/libprojectM/MilkdropPreset/ShapePerFrameContext.hpp @@ -41,7 +41,7 @@ class ShapePerFrameContext /** * @brief Compiles and runs the preset init code. - * @throws MilkdropCompileException Thrown if one of the custom shape init code couldn't be compiled. + * If one of the custom shape init code can't be compiled, a warning is logged and the code is skipped, like in Milkdrop. * @param perFrameInitCode The init code. * @param shape The shape this context belongs to. */ @@ -49,7 +49,7 @@ class ShapePerFrameContext /** * @brief Compiles the per-frame code and stores the code handle in the class. - * @throws MilkdropCompileException Thrown if one of the per-frame code couldn't be compiled. + * If one of the per-frame code can't be compiled, a warning is logged and the code is skipped, like in Milkdrop. * @param perFrameCode The code to compile. * @param shape The shape this context belongs to. */ diff --git a/src/libprojectM/MilkdropPreset/WaveformPerFrameContext.cpp b/src/libprojectM/MilkdropPreset/WaveformPerFrameContext.cpp index c80a006474..0b9e35540e 100644 --- a/src/libprojectM/MilkdropPreset/WaveformPerFrameContext.cpp +++ b/src/libprojectM/MilkdropPreset/WaveformPerFrameContext.cpp @@ -122,7 +122,13 @@ void WaveformPerFrameContext::EvaluateInitCode(const std::string& perFrameInitCo error = "[WaveformPerFrameContext] Could not compile custom wave " + std::to_string(waveform.m_index) + " per-frame init code."; } LOG_DEBUG("[WaveformPerFrameContext] Failed custom wave per-frame INIT code:\n" + perFrameInitCode); - throw MilkdropCompileException(error); + // Like Milkdrop, only this code block is disabled, the preset is still used. + LOG_WARN(error); + for (auto* tVar : t_vars) + { + *tVar = 0; + } + return; } projectm_eval_code_execute(initCode); @@ -157,7 +163,9 @@ void WaveformPerFrameContext::CompilePerFrameCode(const std::string& perFrameCod error = "[WaveformPerFrameContext] Could not compile custom wave " + std::to_string(waveform.m_index) + " per-frame code."; } LOG_DEBUG("[WaveformPerFrameContext] Failed custom wave per-frame code:\n" + perFrameCode); - throw MilkdropCompileException(error); + // Like Milkdrop, only this code block is disabled, the preset is still used. + LOG_WARN(error); + return; } } diff --git a/src/libprojectM/MilkdropPreset/WaveformPerFrameContext.hpp b/src/libprojectM/MilkdropPreset/WaveformPerFrameContext.hpp index 9993f042da..658004f674 100644 --- a/src/libprojectM/MilkdropPreset/WaveformPerFrameContext.hpp +++ b/src/libprojectM/MilkdropPreset/WaveformPerFrameContext.hpp @@ -35,7 +35,7 @@ class WaveformPerFrameContext /** * @brief Compiles and runs the preset init code. - * @throws MilkdropCompileException Thrown if the custom wave init code couldn't be compiled. + * If the custom wave init code can't be compiled, a warning is logged and the code is skipped, like in Milkdrop. * @param perFrameInitCode The init code. * @param waveform The waveform this context belongs to. */ @@ -43,7 +43,7 @@ class WaveformPerFrameContext /** * @brief Compiles the per-frame code and stores the code handle in the class. - * @throws MilkdropCompileException Thrown if the custom wave per-frame code couldn't be compiled. + * If the custom wave per-frame code can't be compiled, a warning is logged and the code is skipped, like in Milkdrop. * @param perFrameCode The code to compile. * @param waveform The waveform this context belongs to. */ diff --git a/src/libprojectM/MilkdropPreset/WaveformPerPointContext.cpp b/src/libprojectM/MilkdropPreset/WaveformPerPointContext.cpp index e73fed4aec..71bac492c3 100644 --- a/src/libprojectM/MilkdropPreset/WaveformPerPointContext.cpp +++ b/src/libprojectM/MilkdropPreset/WaveformPerPointContext.cpp @@ -110,7 +110,9 @@ void WaveformPerPointContext::CompilePerPointCode(const std::string& perPointCod error = "[WaveformPerPointContext] Could not compile custom wave " + std::to_string(waveform.m_index) + " per-point code."; } LOG_DEBUG("[WaveformPerPointContext] Failed custom wave " + std::to_string(waveform.m_index) + " per-point code:\n" + perPointCode); - throw MilkdropCompileException(error); + // Like Milkdrop, only this code block is disabled, the preset is still used. + LOG_WARN(error); + return; } } diff --git a/tests/libprojectM/CMakeLists.txt b/tests/libprojectM/CMakeLists.txt index 29df19fcf3..044e39b55b 100644 --- a/tests/libprojectM/CMakeLists.txt +++ b/tests/libprojectM/CMakeLists.txt @@ -30,6 +30,7 @@ add_executable(projectM-unittest HLSLParserTest.cpp HLSLModuloTest.cpp LoggingTest.cpp + EquationCompileErrorTest.cpp MilkdropShaderCommentParsingTest.cpp PCMTest.cpp PresetFileParserTest.cpp @@ -48,6 +49,7 @@ add_executable(projectM-unittest target_compile_definitions(projectM-unittest PRIVATE PROJECTM_TEST_DATA_DIR="${CMAKE_CURRENT_LIST_DIR}/data" + $ ) # Test includes a header file from libprojectM with its full path in the source dir. @@ -56,6 +58,8 @@ target_include_directories(projectM-unittest "${PROJECTM_SOURCE_DIR}/src/libprojectM" "${PROJECTM_SOURCE_DIR}" "${PROJECTM_SOURCE_DIR}/vendor/hlslparser/src" + # For tests using the expression contexts directly. + $ ) target_link_libraries(projectM-unittest diff --git a/tests/libprojectM/EquationCompileErrorTest.cpp b/tests/libprojectM/EquationCompileErrorTest.cpp new file mode 100644 index 0000000000..576b214512 --- /dev/null +++ b/tests/libprojectM/EquationCompileErrorTest.cpp @@ -0,0 +1,57 @@ +#include + +#include + +#include + +using libprojectM::MilkdropPreset::PerFrameContext; + +namespace { + +/** + * PresetState needs an OpenGL context, so the expression context is set up with its own memory here. + */ +class EquationCompileErrorTest : public testing::Test +{ +protected: + void SetUp() override + { + memory = projectm_eval_memory_buffer_create(); + context = std::make_unique(memory, ®isters); + context->RegisterBuiltinVariables(); + } + + void TearDown() override + { + context.reset(); + projectm_eval_memory_buffer_destroy(memory); + } + + projectm_eval_mem_buffer memory{}; + PRJM_EVAL_F registers[100]{}; + std::unique_ptr context; +}; + +} // namespace + +/** + * Like Milkdrop, a code block that can't be compiled is skipped, and the rest of the preset is still used. + */ +TEST_F(EquationCompileErrorTest, InvalidPerFrameCodeIsSkipped) +{ + // "0.99l" isn't a valid number, so this doesn't compile. + EXPECT_NO_THROW(context->CompilePerFrameCode("additive = above(sin(time * 200), 0.99l);")); + EXPECT_EQ(context->perFrameCodeHandle, nullptr); + + // Executing the skipped code does nothing. + EXPECT_NO_THROW(context->ExecutePerFrameCode()); +} + +TEST_F(EquationCompileErrorTest, ValidPerFrameCodeIsCompiledAndExecuted) +{ + context->CompilePerFrameCode("zoom = 1.25;"); + ASSERT_NE(context->perFrameCodeHandle, nullptr); + + context->ExecutePerFrameCode(); + EXPECT_EQ(*context->zoom, 1.25); +}