Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
116 changes: 114 additions & 2 deletions src/api/include/projectM-4/debug.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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"
Expand Down
2 changes: 2 additions & 0 deletions src/libprojectM/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
129 changes: 129 additions & 0 deletions src/libprojectM/ExpressionVariableWatcher.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
#include "ExpressionVariableWatcher.hpp"

#include "Utils.hpp"

#include <projectM-4/debug.h>

#include <cstdint>
#include <string>
#include <utility>

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
120 changes: 120 additions & 0 deletions src/libprojectM/ExpressionVariableWatcher.hpp
Original file line number Diff line number Diff line change
@@ -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 <projectM-4/debug.h>

#include <cstdint>
#include <exception>
#include <map>
#include <string>
#include <utility>

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<ExpressionBlocks, uint32_t>; //!< 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<VariableName, VariableValues>; //!< 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<IndexedExpressionBlock, BlockWatches> m_watches;
};

} // namespace libprojectM
Loading
Loading