diff --git a/src/api/include/projectM-4/debug.h b/src/api/include/projectM-4/debug.h index 32ff875f6..06199e549 100644 --- a/src/api/include/projectM-4/debug.h +++ b/src/api/include/projectM-4/debug.h @@ -36,7 +36,7 @@ extern "C" { * @brief Writes a .bmp main texture dump after rendering the next main texture, before shaders are applied. * * If no file name is given, the image is written to the current working directory - * and will be named named "frame_texture_contents-YYYY-mm-dd-HH:MM:SS-frame.bmp". + * and will be named "frame_texture_contents-YYYY-mm-dd-HH:MM:SS-frame.bmp". * * Note this is the main texture contents, not the final rendering result. If the active preset * uses a composite shader, the dumped image will not have it applied. The main texture is what is @@ -50,7 +50,119 @@ extern "C" { * @param output_file The filename to write the dump to or NULL. * @since 4.0.0 */ -PROJECTM_EXPORT void projectm_write_debug_image_on_next_frame(projectm_handle instance, const char* output_file); +PROJECTM_EXPORT void projectm_write_debug_image_on_next_frame(projectm_handle instance, + const char* output_file); + +/** + * @brief Structure containing the values of a single expression variable. + * + * This struct is used to return the values of a single, monitored expression variable. Depending + * on how often a certain code block is being executed, this struct will contain one or more values. + * For example, per_frame variables will only have one entry, while variables in custom waveform + * per_point expressions will have as many entries as there were points rendered in the last frame. + * + * For user sprites, @a index is the sprite slot returned by @a projectm_sprite_create(). + * + * If a code block wasn't executed at all, e.g. if a preset doesn't use a certain effect, the count + * will be zero and the values array a NULL pointer. + * + * The application must not change the contents of the returned structure, as it is considered + * read-only. + * + * @note There is no last NULL element in the values array. Use value_count to iterate over the + * correct number of values! + * @since 4.2.0 + */ +struct projectm_expression_variable_values { + uint32_t value_count; //!< The number of entries in @a values. + uint32_t index; //!< The custom shape/waveform or user sprite index. 0 for any other block. + double** values; //!< An array of double pointers, containing the values of the expression values as they were set to at the end of the last rendered frame. +}; + +/** + * @brief Available expression blocks for watches. + * + * Currently, the five code blocks executed per frame are available for watching, plus the Milkdrop + * user sprite per_frame code. + * + * Init blocks cannot be watched, as they are only executed once whenever a preset is loaded, and use + * the same variables as their respective per_frame counterparts. + * + * @since 4.2.0 + */ +typedef enum +{ + PROJECTM_EXPR_PER_FRAME, //!< Variables in the "per_frame_" code block + PROJECTM_EXPR_PER_POINT, //!< Variables in the "per_point_" (AKA per vertex) code block + PROJECTM_EXPR_SHAPE_PER_FRAME, //!< Variables in + PROJECTM_EXPR_WAVE_PER_FRAME, + PROJECTM_EXPR_WAVE_PER_POINT, + PROJECTM_EXPR_MILKDROP_SPRITE +} projectm_expression_blocks; + +/** + * @brief Adds a new variable watch and returns a pointer to the data holder structure. + * + * This function will add a new watch for a single variable in the specified code block. If the code + * block was valid, a pointer to a @a projectm_expression_variable_values will be returned. This + * pointer will stay valid until this specific or all watches are removed, or the watched projectM + * instance is being destroyed. + * + * Only the "active" preset is being watched. During a transition, the newly loaded preset is not + * the active one - it will become active once the transition is finished and the previous preset + * was unloaded. Hard cuts will change the active preset immediately. + * + * Variables which are not used/defined by a preset will always return 0.0. The value count will + * still match the number of block invocations. + * + * User sprites will be watched based on their slot index, as returned by @a projectm_sprite_create(). + * + * Adding watches only have a very small performance impact, with most of the work being done on + * preset load. During runtime, the overhead basically consists of copying the watched double values + * from the expression context into the watch structure. + * + * @note The returned structure is owned by projectM. Do not free the structure externally! + * @param instance The projectM instance handle. + * @param block The code block to add the watch for. + * @param index The custom shape/waveform or user sprite index. Ignored for other block types. + * @param variable_name The name of the variable to watch. + * @return A pointer to a @a projectm_expression_variable_values structure which will contain the + * variable data, or NULL if the watch could not be added. + * @since 4.2.0 + */ +PROJECTM_EXPORT const projectm_expression_variable_values* projectm_expression_watch_add(projectm_handle instance, + projectm_expression_blocks block, + uint32_t index, + const char* variable_name); + +/** + * @brief Removes a previously added variable watch. + * + * @note The returned structure pointer will be invalid after calling this function, and is only + * supplied to match it to a stored value within the application for easier removal. + * @param instance The projectM instance handle. + * @param block The code block to remove the watch from. + * @param index The custom shape/waveform or user sprite index. Ignored for other block types. + * @param variable_name The name of the variable to remove the watch for. + * @return The pointer to the previously registered @a projectm_expression_variable_values structure, + * if the watch was found, or NULL if the watch could not be found and wasn't removed. + * @since 4.2.0 + */ +PROJECTM_EXPORT const projectm_expression_variable_values* projectm_expression_watch_remove(projectm_handle instance, + projectm_expression_blocks block, + uint32_t index, + const char* variable_name); + +/** + * @brief Removes all active variable watches from this projectM instance. + * + * All previously returned pointers to @a projectm_expression_variable_values will no longer be valid + * and thus must not be used/dereferenced after calling this function. + * + * @param instance The projectM instance handle. + * @since 4.2.0 + */ +PROJECTM_EXPORT void projectm_expression_watch_remove_all(projectm_handle instance); #ifdef __cplusplus } // extern "C" diff --git a/src/libprojectM/CMakeLists.txt b/src/libprojectM/CMakeLists.txt index 1e226458f..8d82daa58 100644 --- a/src/libprojectM/CMakeLists.txt +++ b/src/libprojectM/CMakeLists.txt @@ -13,6 +13,8 @@ add_subdirectory(UserSprites) add_library(projectM_main OBJECT "${PROJECTM_EXPORT_HEADER}" + ExpressionVariableWatcher.cpp + ExpressionVariableWatcher.hpp Logging.cpp Logging.hpp Preset.hpp diff --git a/src/libprojectM/ExpressionVariableWatcher.cpp b/src/libprojectM/ExpressionVariableWatcher.cpp new file mode 100644 index 000000000..23162403c --- /dev/null +++ b/src/libprojectM/ExpressionVariableWatcher.cpp @@ -0,0 +1,129 @@ +#include "ExpressionVariableWatcher.hpp" + +#include "Utils.hpp" + +#include + +#include +#include +#include + +namespace libprojectM { + +auto ExpressionVariableWatcher::Add(ExpressionBlocks block, uint32_t index, VariableName variableName) -> const ExpressionVariableWatcher::VariableValues* +{ + // Validate block value only, set index to 0 in blocks only occurring once. + switch (block) + { + case PROJECTM_EXPR_PER_FRAME: + case PROJECTM_EXPR_PER_POINT: + index = 0; + + case PROJECTM_EXPR_SHAPE_PER_FRAME: + case PROJECTM_EXPR_WAVE_PER_FRAME: + case PROJECTM_EXPR_WAVE_PER_POINT: + case PROJECTM_EXPR_MILKDROP_SPRITE: + break; + + default: + return nullptr; + } + + libprojectM::Utils::ToLowerInPlace(variableName); + + IndexedExpressionBlock const indexedBlock = std::make_pair(block, index); + + auto blockInMap = m_watches.find(indexedBlock); + if (blockInMap == m_watches.end()) + { + blockInMap = m_watches.insert({indexedBlock, {}}).first; + } + + // If the watch was already registered with the given, return the existing pointer again. + auto variableWatch = blockInMap->second.find(variableName); + if (variableWatch != blockInMap->second.end()) + { + return &variableWatch->second; + } + + // New watch. Create a new value struct and return it. + return &blockInMap->second.insert({variableName, {}}).first->second; +} + +auto ExpressionVariableWatcher::Remove(ExpressionBlocks block, uint32_t index, VariableName variableName) -> const ExpressionVariableWatcher::VariableValues* +{ + // Validate block value only, set index to 0 in blocks only occurring once. + switch (block) + { + case PROJECTM_EXPR_PER_FRAME: + case PROJECTM_EXPR_PER_POINT: + index = 0; + + case PROJECTM_EXPR_SHAPE_PER_FRAME: + case PROJECTM_EXPR_WAVE_PER_FRAME: + case PROJECTM_EXPR_WAVE_PER_POINT: + case PROJECTM_EXPR_MILKDROP_SPRITE: + break; + + default: + return nullptr; + } + + libprojectM::Utils::ToLowerInPlace(variableName); + + IndexedExpressionBlock const indexedBlock = std::make_pair(block, index); + + auto blockInMap = m_watches.find(indexedBlock); + if (blockInMap == m_watches.end()) + { + return nullptr; + } + + auto variableWatch = blockInMap->second.find(variableName); + if (variableWatch == blockInMap->second.end()) + { + return nullptr; + } + + // Watch found, make a copy of the values struct pointer, then delete it. + const auto* oldValuesStruct = &variableWatch->second; + + blockInMap->second.erase(variableWatch); + + return oldValuesStruct; +} + +void ExpressionVariableWatcher::Clear() +{ + m_watches.clear(); +} + +auto ExpressionVariableWatcher::GetBlockWatches(ExpressionBlocks block, uint32_t index) -> BlockWatches& +{ + // Validate block value only. + switch (block) + { + case PROJECTM_EXPR_PER_FRAME: + case PROJECTM_EXPR_PER_POINT: + case PROJECTM_EXPR_SHAPE_PER_FRAME: + case PROJECTM_EXPR_WAVE_PER_FRAME: + case PROJECTM_EXPR_WAVE_PER_POINT: + case PROJECTM_EXPR_MILKDROP_SPRITE: + break; + + default: + throw InvalidBlockException("Block type " + std::to_string(block) + " is not supported!"); + } + + IndexedExpressionBlock const indexedBlock = std::make_pair(block, index); + + auto blockInMap = m_watches.find(indexedBlock); + if (blockInMap == m_watches.end()) + { + blockInMap = m_watches.insert({indexedBlock, {}}).first; + } + + return blockInMap->second; +} + +} // namespace libprojectM diff --git a/src/libprojectM/ExpressionVariableWatcher.hpp b/src/libprojectM/ExpressionVariableWatcher.hpp new file mode 100644 index 000000000..972c61036 --- /dev/null +++ b/src/libprojectM/ExpressionVariableWatcher.hpp @@ -0,0 +1,120 @@ +/* +* projectM -- Milkdrop-esque visualisation SDK + * Copyright (C)2003-2007 projectM Team + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * See 'LICENSE.txt' included within this release + * + */ +#pragma once + +#include + +#include +#include +#include +#include +#include + +namespace libprojectM { + +/** + * @class ExpressionVariableWatcher + * @brief Container class managing active expression variable watches. + * + * All watches are stored in a three-tiered map, with the first map using the block and index as key. + * The second level map associated each watched variable to the struct which will receive the + * values. This struct and is also used by the outside application to display or otherwise evaluate + * the values after a frame is rendered. + */ +class ExpressionVariableWatcher +{ +public: + /** + * @brief Exception for an invalid block argument in GetBlockWatches(). + */ + class InvalidBlockException : public std::exception + { + public: + InvalidBlockException(std::string message) + : m_message(std::move(message)) + { + } + + ~InvalidBlockException() override = default; + + auto what() const noexcept -> const char* override + { + return m_message.c_str(); + } + + auto message() const -> const std::string& + { + return m_message; + } + + private: + std::string m_message; + }; + + using ExpressionBlocks = projectm_expression_blocks; //!< Alias for the projectm_expression_blocks public API enum. + using IndexedExpressionBlock = std::pair; //!< A pair if ExpressionBlocks and the custom shape/waveform or sprite index. + using VariableName = std::string; //!< the name of a watched variable, lower-case. + using VariableValues = projectm_expression_variable_values; //!< Alias for the projectm_expression_variable_values public API struct. + using BlockWatches = std::map; //!< A map of variable names to the associated value structs. + + /** + * @brief Adds a new watch, or returns a pointer to the previously registered one. + * @param block The expression code block to add the watch for. + * @param index The custom shape/waveform or user sprite index. + * @param variableName The variable to watch. + * @return A pointer to the newly added values struct, if the watch was successfully added. + */ + auto Add(ExpressionBlocks block, uint32_t index, VariableName variableName) -> const VariableValues*; + + /** + * @brief Removes a previously registered variable watch. + * @param block The expression code block to remove the watch from. + * @param index The custom shape/waveform or user sprite index. + * @param variableName The variable to unwatch. + * @return If the previous watch was found, a pointer to the initially registered values struct, + * or nullptr if no watch was found. + */ + auto Remove(ExpressionBlocks block, uint32_t index, VariableName variableName) -> const VariableValues*; + + /** + * Clears all previously registered watches. + */ + void Clear(); + + /** + * @brief Returns all watched variables and their value structs for the given block and index. + * @throws InvalidBlockException Thrown if the passed block value is invalid. + * @param block The expression code block to return the watch list for. + * @param index The custom shape/waveform or user sprite index to return the watch list for. + * @return A reference to a map of type BlockWatches with all registered watched variables. + */ + auto GetBlockWatches(ExpressionBlocks block, uint32_t index) -> BlockWatches&; + +private: + /** + * @brief Registered watches. + * + * A three-tiered map of indexed blocks, variables and the value struct returned to the application. + */ + std::map m_watches; +}; + +} // namespace libprojectM diff --git a/src/libprojectM/MilkdropPreset/MilkdropPreset.cpp b/src/libprojectM/MilkdropPreset/MilkdropPreset.cpp index ef1716115..7923cc01b 100755 --- a/src/libprojectM/MilkdropPreset/MilkdropPreset.cpp +++ b/src/libprojectM/MilkdropPreset/MilkdropPreset.cpp @@ -187,6 +187,14 @@ void MilkdropPreset::BindFramebuffer() } } +void MilkdropPreset::UpdateWatches(ExpressionVariableWatcher& watcher) +{ +} + +void MilkdropPreset::RemoveWatches() +{ +} + void MilkdropPreset::PerFrameUpdate() { m_perFrameContext.LoadStateVariables(m_state); @@ -227,7 +235,7 @@ void MilkdropPreset::Load(std::istream& stream) if (!parser.Read(stream)) { - const std::string error = "[MilkdropPreset] Could not parse preset data."; + const std::string error = "[MilkdropPreset] Could not parse preset data."; LOG_ERROR(error) throw MilkdropPresetLoadException(error); } diff --git a/src/libprojectM/MilkdropPreset/MilkdropPreset.hpp b/src/libprojectM/MilkdropPreset/MilkdropPreset.hpp index db8eb43ba..2f7ad00ef 100644 --- a/src/libprojectM/MilkdropPreset/MilkdropPreset.hpp +++ b/src/libprojectM/MilkdropPreset/MilkdropPreset.hpp @@ -24,6 +24,7 @@ #pragma once #include "Border.hpp" +#include "Constants.hpp" #include "CustomShape.hpp" #include "CustomWaveform.hpp" #include "DarkenCenter.hpp" @@ -32,29 +33,35 @@ #include "PerFrameContext.hpp" #include "PerPixelContext.hpp" #include "PerPixelMesh.hpp" -#include "Preset.hpp" #include "Waveform.hpp" +#include +#include + +#include