initial import, >900commits predate this

This commit is contained in:
2020-09-29 13:47:50 +02:00
commit e74154ccee
352 changed files with 108120 additions and 0 deletions

23
framework/CMakeLists.txt Normal file
View File

@ -0,0 +1,23 @@
cmake_minimum_required(VERSION 3.2)
project(framework CXX)
add_subdirectory(engine)
add_subdirectory(logger)
add_subdirectory(resource_manager)
add_subdirectory(common_components)
add_subdirectory(std_utils)
add_subdirectory(screen_director)
add_subdirectory(filesystem)
add_subdirectory(simple_scene)
if(NOT MM_HEADLESS)
add_subdirectory(sdl_service)
add_subdirectory(simple_sdl_renderer)
add_subdirectory(opengl_renderer)
add_subdirectory(opengl_primitives)
add_subdirectory(input)
add_subdirectory(sound)
add_subdirectory(imgui)
add_subdirectory(tilemap)
endif()

View File

@ -0,0 +1,28 @@
cmake_minimum_required(VERSION 3.2)
project(common_components CXX)
add_library(common_components INTERFACE)
target_include_directories(common_components INTERFACE "${CMAKE_CURRENT_SOURCE_DIR}/src")
target_link_libraries(common_components INTERFACE
entt
glm
)
##########################
add_library(common_components_serialize_json INTERFACE)
target_include_directories(common_components_serialize_json INTERFACE "${CMAKE_CURRENT_SOURCE_DIR}/src")
target_link_libraries(common_components_serialize_json INTERFACE
common_components
nlohmann_json::nlohmann_json
)
if(BUILD_TESTING)
add_subdirectory(test)
endif()

View File

@ -0,0 +1,10 @@
#pragma once
#include <glm/vec4.hpp>
namespace MM::Components {
struct Color {
glm::vec4 color {1.f, 1.f, 1.f, 1.f};
};
}

View File

@ -0,0 +1,11 @@
#pragma once
#include <string>
namespace MM::Components {
struct Name {
static const size_t max_str_len = 64;
std::string str;
};
}

View File

@ -0,0 +1,12 @@
#pragma once
#include <nlohmann/json.hpp>
#include <mm/components/color.hpp>
#include "./json_glm.hpp"
namespace MM::Components {
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(Color, color)
} // MM::Components

View File

@ -0,0 +1,16 @@
#pragma once
#include <glm/vec2.hpp>
#include <glm/vec3.hpp>
#include <glm/vec4.hpp>
#include <nlohmann/json.hpp>
namespace glm {
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(vec2, x, y)
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(vec3, x, y, z)
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(vec4, x, y, z, w)
}

View File

@ -0,0 +1,10 @@
#pragma once
#include <nlohmann/json.hpp>
#include <mm/components/name.hpp>
namespace MM::Components {
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(Name, str)
} // MM::Components

View File

@ -0,0 +1,12 @@
#pragma once
#include <nlohmann/json.hpp>
#include <mm/components/transform2d.hpp>
#include "./json_glm.hpp"
namespace MM::Components {
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(Transform2D, position, scale, rotation)
} // MM::Components

View File

@ -0,0 +1,12 @@
#pragma once
#include <nlohmann/json.hpp>
#include <mm/components/transform3d.hpp>
#include "./json_glm.hpp"
namespace MM::Components {
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(Transform3D, position, scale, rotation)
} // MM::Components

View File

@ -0,0 +1,12 @@
#pragma once
#include <nlohmann/json.hpp>
#include <mm/components/velocity2d.hpp>
#include "./json_glm.hpp"
namespace MM::Components {
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(Velocity2D, velocity, rotation)
} // MM::Components

View File

@ -0,0 +1,12 @@
#pragma once
#include <nlohmann/json.hpp>
#include <mm/components/velocity3d.hpp>
#include "./json_glm.hpp"
namespace MM::Components {
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(Velocity3D, velocity, rotation)
} // MM::Components

View File

@ -0,0 +1,10 @@
#pragma once
#include <nlohmann/json.hpp>
#include <mm/components/view_dir2d.hpp>
namespace MM::Components {
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(ViewDir2D, dir)
} // MM::Components

View File

@ -0,0 +1,10 @@
#pragma once
#include <nlohmann/json.hpp>
#include <mm/components/view_dir3d.hpp>
namespace MM::Components {
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(ViewDir3D, yaw, pitch, roll)
} // MM::Components

View File

@ -0,0 +1,55 @@
//
// Created by FlaXxy on 29.07.2018.
//
#pragma once
#include <glm/vec2.hpp>
#include <glm/mat3x3.hpp>
#include <glm/mat4x4.hpp>
#include <glm/gtc/matrix_transform.hpp>
namespace MM::Components {
struct Transform2D {
glm::vec2 position {0.0f,0.0f};
glm::vec2 scale {1.0f,1.0f};
float rotation {0.0f};
glm::mat3x3 getTransform3(void) const {
//return transformationMatrix(scale, rotation, position);
glm::mat3x3 res(1);
res[2][0] = position.x;
res[2][1] = position.y;
float const s = sinf(rotation);
float const c = cosf(rotation);
res[0][0] = scale.x * c;
res[0][1] = scale.x * s;
res[1][0] = scale.y * -s;
res[1][1] = scale.y * c;
return res;
}
glm::mat4x4 getTransform4(float z = 500.f) const {
//return transformationMatrix(scale, rotation, position);
glm::mat4x4 res{1};
//res[2][0] = position.x;
//res[2][1] = position.y;
res = glm::translate(res, glm::vec3(position, z));
//float const s = sinf(rotation);
//float const c = cosf(rotation);
//res[0][0] = scale.x * c;
//res[0][1] = scale.x * s;
//res[1][0] = scale.y * -s;
//res[1][1] = scale.y * c;
res = glm::rotate(res, rotation, glm::vec3(0.f, 0.f, 1.f));
res = glm::scale(res, glm::vec3(scale, 1.f));
return res;
}
};
} // MM::Components

View File

@ -0,0 +1,37 @@
#pragma once
//#include <glm/mat3x3.hpp>
#include <glm/mat4x4.hpp>
#include <glm/gtc/matrix_transform.hpp>
//#include <entt/core/type_traits.hpp>
namespace MM::Components {
struct Transform3D {
glm::vec3 position {0.0f,0.0f,0.0f};
glm::vec3 scale {1.0f,1.0f,1.0f};
float rotation {0.0f};
glm::mat4x4 getTransform4(void) const {
//return transformationMatrix(scale, rotation, position);
glm::mat4x4 res{1};
//res[2][0] = position.x;
//res[2][1] = position.y;
res = glm::translate(res, position);
//float const s = sinf(rotation);
//float const c = cosf(rotation);
//res[0][0] = scale.x * c;
//res[0][1] = scale.x * s;
//res[1][0] = scale.y * -s;
//res[1][1] = scale.y * c;
res = glm::rotate(res, rotation, glm::vec3(0.f, 0.f, 1.f));
res = glm::scale(res, scale);
return res;
}
};
} // MM::Components

View File

@ -0,0 +1,16 @@
//
// Created by FlaXxy on 29.07.2018.
//
#pragma once
#include <glm/vec2.hpp>
namespace MM::Components {
struct Velocity2D {
glm::vec2 velocity;
float rotation {0.f};
};
} // MM::Components

View File

@ -0,0 +1,13 @@
#pragma once
#include <glm/vec3.hpp>
namespace MM::Components {
struct Velocity3D {
glm::vec3 velocity {0.f, 0.f, 0.f};
glm::vec3 rotation {0.f, 0.f, 0.f};
};
} // MM::Components

View File

@ -0,0 +1,10 @@
#pragma once
namespace MM::Components {
struct ViewDir2D {
float dir = 0.f; // rad
};
} // MM::Components

View File

@ -0,0 +1,12 @@
#pragma once
namespace MM::Components {
struct ViewDir3D {
float yaw = 0.f; // rad
float pitch = 0.f; // rad
float roll = 0.f; // rad
};
} // MM::Components

View File

@ -0,0 +1,16 @@
add_executable(common_component_json_serialization_test
component_json_serialization_test.cpp
)
target_include_directories(common_component_json_serialization_test PRIVATE ".")
target_link_libraries(common_component_json_serialization_test
common_components_serialize_json
engine
gtest_main
)
add_test(NAME common_component_json_serialization_test COMMAND common_component_json_serialization_test)

View File

@ -0,0 +1,270 @@
#include <gtest/gtest.h>
#include <mm/components/serialize/json_name.hpp>
#include <mm/components/serialize/json_color.hpp>
#include <mm/components/serialize/json_transform2d.hpp>
#include <mm/components/serialize/json_transform3d.hpp>
#include <mm/components/serialize/json_velocity2d.hpp>
#include <mm/components/serialize/json_view_dir2d.hpp>
#include <mm/components/serialize/json_view_dir3d.hpp>
/*#define PARSE_TEST_MACRO(type, json_string, comp_val) \
TEST(common_components_json_serialization, type) { \
MM::Components::type comp; \
{ \
auto j = nlohmann::json::parse(json_string); \
EXPECT_NO_THROW({ comp = j; }); \
ASSERT_EQ(comp, (comp_val)); \
} \
{ \
nlohmann::json j; \
[>EXPECT_NO_THROW({ j = comp; });<] \
ASSERT_EQ(json_string, j.dump()); \
} \
}
PARSE_TEST_MACRO(
Name,
"{\"str\":\"test_name\"}",
MM::Components::Name{"test_name"}
); */
TEST(common_components_json_serialization, in_out_name) {
MM::Components::Name comp;
const char* json_test_file = "{\"str\":\"test_name\"}";
{ // in
auto j = nlohmann::json::parse(json_test_file);
EXPECT_NO_THROW({
comp = j;
});
ASSERT_EQ(comp.str, "test_name");
}
{ // out
nlohmann::json j;
EXPECT_NO_THROW({
j = comp;
});
ASSERT_EQ(json_test_file, j.dump());
}
}
TEST(common_components_json_serialization, in_out_name_fail) {
MM::Components::Name name_comp;
// intentional malformed json string
const char* json_test_file = "{\"strasdf\":\"test_name\"}";
{ // in
auto j = nlohmann::json::parse(json_test_file);
ASSERT_ANY_THROW({
name_comp = j;
});
}
}
// ##############################################################
TEST(common_components_json_serialization, in_out_color) {
MM::Components::Color comp;
const char* json_test_file = R"({"color":{"w":1337.0,"x":0.0,"y":1.0,"z":3.0}})";
{ // in
auto j = nlohmann::json::parse(json_test_file);
EXPECT_NO_THROW({
comp = j;
});
glm::vec4 comp_val{0.f, 1.f, 3.f, 1337.f};
ASSERT_EQ(comp.color.x, comp_val.x);
ASSERT_EQ(comp.color.y, comp_val.y);
ASSERT_EQ(comp.color.z, comp_val.z);
ASSERT_EQ(comp.color.w, comp_val.w);
}
{ // out
nlohmann::json j;
EXPECT_NO_THROW({
j = comp;
});
ASSERT_EQ(json_test_file, j.dump());
}
}
// ##############################################################
TEST(common_components_json_serialization, in_out_transform2d) {
MM::Components::Transform2D comp;
const char* json_test_file = R"({"position":{"x":42.0,"y":6.0},"rotation":99.0,"scale":{"x":1337.0,"y":68.0}})";
{ // in
auto j = nlohmann::json::parse(json_test_file);
EXPECT_NO_THROW({
comp = j;
});
ASSERT_EQ(comp.position.x, 42.f);
ASSERT_EQ(comp.position.y, 6.f);
ASSERT_EQ(comp.scale.x, 1337.f);
ASSERT_EQ(comp.scale.y, 68.f);
ASSERT_EQ(comp.rotation, 99.f);
}
{ // out
nlohmann::json j;
EXPECT_NO_THROW({
j = comp;
});
ASSERT_EQ(json_test_file, j.dump());
}
}
// ##############################################################
TEST(common_components_json_serialization, in_out_transform3d) {
MM::Components::Transform3D comp;
const char* json_test_file = R"({"position":{"x":42.0,"y":6.0,"z":66.0},"rotation":99.0,"scale":{"x":1337.0,"y":68.0,"z":60.0}})";
{ // in
auto j = nlohmann::json::parse(json_test_file);
EXPECT_NO_THROW({
comp = j;
});
ASSERT_EQ(comp.position.x, 42.f);
ASSERT_EQ(comp.position.y, 6.f);
ASSERT_EQ(comp.position.z, 66.f);
ASSERT_EQ(comp.scale.x, 1337.f);
ASSERT_EQ(comp.scale.y, 68.f);
ASSERT_EQ(comp.scale.z, 60.f);
// TODO: prob needs 3 rotations...
ASSERT_EQ(comp.rotation, 99.f);
}
{ // out
nlohmann::json j;
EXPECT_NO_THROW({
j = comp;
});
ASSERT_EQ(json_test_file, j.dump());
}
}
// ##############################################################
TEST(common_components_json_serialization, in_out_velocity2d) {
MM::Components::Velocity2D comp;
const char* json_test_file = R"({"rotation":99.0,"velocity":{"x":42.0,"y":6.0}})";
{ // in
auto j = nlohmann::json::parse(json_test_file);
EXPECT_NO_THROW({
comp = j;
});
ASSERT_EQ(comp.velocity.x, 42.f);
ASSERT_EQ(comp.velocity.y, 6.f);
ASSERT_EQ(comp.rotation, 99.f);
}
{ // out
nlohmann::json j;
EXPECT_NO_THROW({
j = comp;
});
ASSERT_EQ(json_test_file, j.dump());
}
}
// ##############################################################
TEST(common_components_json_serialization, in_out_view_dir2d) {
MM::Components::ViewDir2D comp;
const char* json_test_file = R"({"dir":6.0})";
{ // in
auto j = nlohmann::json::parse(json_test_file);
EXPECT_NO_THROW({
comp = j;
});
ASSERT_EQ(comp.dir, 6.f);
}
{ // out
nlohmann::json j;
EXPECT_NO_THROW({
j = comp;
});
ASSERT_EQ(json_test_file, j.dump());
}
}
// ##############################################################
TEST(common_components_json_serialization, in_out_view_dir3d) {
MM::Components::ViewDir3D comp;
const char* json_test_file = R"({"pitch":6.0,"roll":99.0,"yaw":42.0})";
{ // in
auto j = nlohmann::json::parse(json_test_file);
EXPECT_NO_THROW({
comp = j;
});
ASSERT_EQ(comp.yaw, 42.f);
ASSERT_EQ(comp.pitch, 6.f);
ASSERT_EQ(comp.roll, 99.f);
}
{ // out
nlohmann::json j;
EXPECT_NO_THROW({
j = comp;
});
ASSERT_EQ(json_test_file, j.dump());
}
}
int main(int argc, char** argv) {
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}

View File

@ -0,0 +1,34 @@
cmake_minimum_required(VERSION 3.2)
project(engine CXX)
add_library(engine
src/mm/engine_fwd.hpp
src/mm/engine.hpp
src/mm/engine.cpp
src/mm/services/service.hpp
src/mm/services/default_service.cpp
src/mm/services/default_service.hpp
src/mm/services/scene_service_interface.hpp
src/mm/services/scene_service_interface.cpp
src/mm/services/net_channeled_interface.hpp
)
# find . -type f -exec sed -i 's/simple_scene\/services\/simple_scene_ss.hpp/services\/scene_service_interface.hpp/g' {} \;
target_include_directories(engine PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/src")
target_link_libraries(engine
tracy_client
logger
entt
glm
)
if (BUILD_TESTING)
add_subdirectory(test)
endif()

View File

@ -0,0 +1,263 @@
#include "./engine.hpp"
#include <chrono>
#include <algorithm>
#include <memory>
#include <tracy/Tracy.hpp>
#include <mm/logger.hpp>
#define LOG_CRIT(...) __LOG_CRIT( "Engine", __VA_ARGS__)
#define LOG_ERROR(...) __LOG_ERROR("Engine", __VA_ARGS__)
#define LOG_WARN(...) __LOG_WARN( "Engine", __VA_ARGS__)
#define LOG_INFO(...) __LOG_INFO( "Engine", __VA_ARGS__)
#define LOG_DEBUG(...) __LOG_DEBUG("Engine", __VA_ARGS__)
#define LOG_TRACE(...) __LOG_TRACE("Engine", __VA_ARGS__)
namespace MM {
Engine::FunctionDataHandle Engine::addUpdate(std::function<void(Engine&)> function) {
if (!function) {
LOG_ERROR("could not add Update, empty function!");
return {};
}
FunctionDataHandle r = _update_functions.emplace_back(std::make_shared<FunctionDataType>(function));
_update_functions_modified = true;
return r;
}
Engine::FunctionDataHandle Engine::addFixedUpdate(std::function<void(Engine&)> function) {
if (!function) {
LOG_ERROR("could not add fixedUpdate, empty function!");
return {};
}
FunctionDataHandle r = _fixed_update_functions.emplace_back(std::make_shared<FunctionDataType>(function));
_fixed_update_functions_modified = true;
return r;
}
void Engine::removeUpdate(FunctionDataHandle handle) {
if (handle.expired()) {
LOG_ERROR("could not remove Update, invalid handle!");
return;
}
auto lock = handle.lock();
auto it = std::find(_update_functions.begin(), _update_functions.end(), lock);
if (it != _update_functions.end()) {
_update_functions.erase(it);
} else {
LOG_ERROR("could not remove Update, unknown handle!");
}
}
void Engine::removeFixedUpdate(FunctionDataHandle handle) {
if (handle.expired()) {
LOG_ERROR("could not remove fixedUpdate, invalid handle!");
return;
}
auto lock = handle.lock();
auto it = std::find(_fixed_update_functions.begin(), _fixed_update_functions.end(), lock);
if (it != _fixed_update_functions.end()) {
_fixed_update_functions.erase(it);
} else {
LOG_ERROR("could not remove fixedUpdate, unknown handle!");
}
}
void Engine::addFixedDefer(std::function<void(Engine&)> function) {
_fixed_defered.emplace_back(function);
}
void Engine::traverseUpdateFunctions(std::vector<std::shared_ptr<FunctionDataType>>& list) {
for (auto& entry : list) {
entry->f(*this);
}
}
Engine::Engine(float f_delta_time) : _fixed_delta_time(f_delta_time) {
if (!MM::Logger::initialized) {
MM::Logger::init();
}
MM::Logger::initSectionLogger("Engine");
}
Engine::~Engine(void) {
cleanup();
}
void Engine::cleanup(void) {
// disable all enabled services
// in reverse order
for (auto s_it = _service_enable_order.rbegin(); s_it != _service_enable_order.rend(); s_it++) {
auto& s_e = _services[*s_it];
if (s_e.get()->first) {
if (auto* ss = s_e.get()->second.get(); ss != nullptr) {
ss->disable(*this);
LOG_INFO("- disabled Service '{}'", ss->name());
}
}
}
_service_enable_order.clear();
_update_functions.clear();
_fixed_update_functions.clear();
spdlog::get("Engine")->flush();
}
void Engine::update(void) {
FrameMarkStart("update")
if (_update_functions_modified) {
ZoneScopedN("MM::Engine::update::sort_update_functions")
std::sort(_update_functions.begin(), _update_functions.end(), [](const auto& a, const auto& b) { return a->priority > b->priority; });
_update_functions_modified = false;
}
{
ZoneScopedN("MM::Engine::update::traverseUpdateFunctions")
traverseUpdateFunctions(_update_functions);
}
FrameMarkEnd("update")
}
void Engine::fixedUpdate(void) {
FrameMarkStart("fixedUpdate")
if (_fixed_update_functions_modified) {
ZoneScopedN("MM::Engine::fixedUpdate::sort_update_functions")
std::sort(_fixed_update_functions.begin(), _fixed_update_functions.end(), [](const auto& a, const auto& b) { return a->priority > b->priority; });
_fixed_update_functions_modified = false;
}
{
ZoneScopedN("MM::Engine::fixedUpdate::traverseUpdateFunctions")
traverseUpdateFunctions(_fixed_update_functions);
}
if (!_fixed_defered.empty()) {
ZoneScopedN("MM::Engine::fixedUpdate::defered")
for (auto& fn : _fixed_defered) {
fn(*this);
}
_fixed_defered.clear();
}
FrameMarkEnd("fixedUpdate")
}
#ifdef __EMSCRIPTEN__
#include <emscripten.h>
static void emscripten_update(void* arg) {
using clock = std::chrono::high_resolution_clock;
static long long int accumulator = 0;
static auto now = clock::now();
auto* e = (MM::Engine*)arg;
auto newNow = clock::now();
auto deltaTime = std::chrono::duration_cast<std::chrono::nanoseconds>(newNow - now);
now = newNow;
accumulator += deltaTime.count();
auto dt = e->getFixedDeltaTime() * 1'000'000'000.0f;
while (accumulator >= dt) {
accumulator -= dt;
e->fixedUpdate();
}
e->update();
}
#endif
void Engine::run(void) {
#ifdef __EMSCRIPTEN__
emscripten_set_main_loop_arg(emscripten_update, this, 0, 1);
#else
using clock = std::chrono::high_resolution_clock;
_is_running = true;
long long int accumulator = 0;
auto now = clock::now();
while (_is_running) {
auto newNow = clock::now();
auto deltaTime = std::chrono::duration_cast<std::chrono::nanoseconds>(newNow - now);
now = newNow;
accumulator += deltaTime.count();
auto dt = _fixed_delta_time * 1'000'000'000.0f;
size_t continuous_counter = 0;
while (accumulator >= dt) {
continuous_counter++;
accumulator -= static_cast<long long int>(dt);
fixedUpdate();
}
if (continuous_counter > 2) {
LOG_WARN("had {} contiguous fixedUpdates!", std::to_string(continuous_counter));
}
update();
}
#endif
}
void Engine::stop(void) {
_is_running = false;
}
bool Engine::enableService(service_family::family_type s_t) {
if (_services.count(s_t)) {
auto* ss_entry = _services[s_t].get();
if (ss_entry->first) {
return false; // already enabled
}
_service_enable_order.emplace_back(s_t); // TODO: make sure
return ss_entry->first = ss_entry->second.get()->enable(*this);
}
// not found
assert(false && "first add Service");
return false;
}
void Engine::disableService(service_family::family_type s_t) {
if (_services.count(s_t)) {
auto* s_entry = _services[s_t].get();
if (s_entry->first) {
s_entry->first = false;
s_entry->second.get()->disable(*this);
//_service_enable_order.emplace_back(service_family::type<T>);
auto it = std::find(_service_enable_order.begin(), _service_enable_order.end(), s_t);
if (it != _service_enable_order.end()) {
_service_enable_order.erase(it);
}
}
}
}
bool Engine::provide(service_family::family_type I, service_family::family_type T) {
if (!_services.count(T)) {
// TODO: log error
return false;
}
_services[I] = _services[T];
return true;
}
} // MM

View File

@ -0,0 +1,182 @@
#pragma once
#include <entt/core/family.hpp>
#include <functional>
#include <memory>
#include <type_traits>
#include <unordered_map>
#include <utility>
#include <vector>
#include <string>
#include <cassert>
#include <mm/services/service.hpp>
namespace MM {
class Engine {
private:
using service_family = entt::family<struct internal_service_family>;
public:
using service_family_type = service_family::family_type;
// the services "internal" interface
//private:
public:
struct FunctionPriorityDataStructure {
std::function<void(Engine&)> f;
int16_t priority = 0; // 0 is equal to scene update, the higher the prio the earlier
std::string name;
explicit FunctionPriorityDataStructure(std::function<void(Engine&)> fun) : f(fun) {}
FunctionPriorityDataStructure(const FunctionPriorityDataStructure& rhs)
: f(rhs.f), priority(rhs.priority), name(rhs.name) {}
};
using FunctionDataType = FunctionPriorityDataStructure;
//using FunctionDataHandle = FunctionDataType*; // important: its a pointer
using FunctionDataHandle = std::weak_ptr<FunctionDataType>; // important: its a pointer
// return nullptr on error
[[nodiscard]] FunctionDataHandle addUpdate(std::function<void(Engine&)> function);
[[nodiscard]] FunctionDataHandle addFixedUpdate(std::function<void(Engine&)> function);
void removeUpdate(FunctionDataHandle handle);
void removeFixedUpdate(FunctionDataHandle handle);
// dont use, if you are not using it to modify the engine.
// you usualy dont need to use this, if you think you need to use this, you probably dont.
void addFixedDefer(std::function<void(Engine&)> function);
private:
std::vector<std::shared_ptr<FunctionDataType>> _update_functions;
bool _update_functions_modified;
std::vector<std::shared_ptr<FunctionDataType>> _fixed_update_functions;
bool _fixed_update_functions_modified;
std::vector<std::function<void(Engine&)>> _fixed_defered;
private:
void traverseUpdateFunctions(std::vector<std::shared_ptr<FunctionDataType>>& list); // traverses an update list, gets called by update()/fixedUpdate()
private:
volatile bool _is_running = false;
const float _fixed_delta_time;
public:
explicit Engine(float f_delta_time = 1.f/60.f);
~Engine(void);
// called from destructor or explicitly
void cleanup(void);
[[nodiscard]] float getFixedDeltaTime(void) const { return _fixed_delta_time; };
void update(void);
void fixedUpdate(void);
void run(void); // calls update()/fixedUpdate() until stopped
void stop(void);
private:
std::vector<service_family::family_type> _service_add_order; // ?
std::vector<service_family::family_type> _service_enable_order; // ?
//std::unordered_map<service_family::family_type, std::pair<bool, std::unique_ptr<Service>>> _services;
std::unordered_map<
service_family::family_type,
std::shared_ptr<std::pair<
bool,
std::unique_ptr<Services::Service>
>>
> _services;
// maps I to T
//std::unordered_map<service_family::family_type, service_family::family_type> _implementation_provider;
public:
template<typename T>
constexpr auto type(void) {
return service_family::type<T>;
}
template<typename T, typename... Args>
T& addService(Args&& ... args) {
assert(!tryService<T>());
auto& ss_entry = _services[service_family::type<T>] =
std::make_shared<std::pair<bool, std::unique_ptr<Services::Service>>>(
std::make_pair<bool, std::unique_ptr<Services::Service>>(
false,
std::make_unique<T>(std::forward<Args>(args)...)
)
);
_service_add_order.emplace_back(service_family::type<T>);
return (T&)*ss_entry.get()->second.get();
}
template<typename T>
[[nodiscard]] T* tryService(void) const {
if (_services.count(service_family::type<T>)) {
return static_cast<T*>(_services.at(service_family::type<T>).get()->second.get());
}
return nullptr;
}
template<typename T>
[[deprecated("use tryService() instead")]]
[[nodiscard]] T* tryGetService(void) const {
return tryService<T>();
}
template<typename T>
[[nodiscard]] T& getService(void) const {
T* tmp = tryService<T>();
assert(tmp);
return *tmp;
}
bool enableService(service_family::family_type s_t);
void disableService(service_family::family_type s_t);
template<typename T>
bool enableService(void) {
return enableService(service_family::type<T>);
}
template<typename T>
void disableService(void) {
disableService(service_family::type<T>);
}
// provide T as I implementation
// T needs to be an added Service
bool provide(service_family::family_type I, service_family::family_type T);
// provide T as I implementation
// T needs to be an added Service
template<typename I, typename T>
bool provide(void) {
static_assert(std::is_base_of_v<I, T>, "T is not derived from I!");
static_assert(!std::is_same_v<I, T>, "I and T are the same, makes no sense!");
return provide(service_family::type<I>, service_family::type<T>);
}
// TODO: reimplement???
//template<typename I>
//void removeProvider(void) {
//if (auto it = _implementation_provider.find(service_family::type<I>); it != _implementation_provider.end()) {
//_implementation_provider.erase(it);
//}
//}
};
} // MM

View File

@ -0,0 +1,12 @@
#pragma once
// this is a forwarding header
#include <entt/fwd.hpp>
namespace MM {
class Engine;
using Entity = entt::entity;
using Scene = entt::basic_registry<Entity>;
}

View File

@ -0,0 +1,52 @@
#include "./default_service.hpp"
namespace MM::Services {
// TODO: error handling
bool DefaultService::enable(Engine& engine) {
{
_func_handles[0] = engine.addUpdate([this](Engine& e) { this->preSceneUpdate(e); });
auto tmp_lock = _func_handles[0].lock();
tmp_lock->priority = 1;
tmp_lock->name = "DefaultService::preSceneUpdate";
}
{
_func_handles[1] = engine.addUpdate([this](Engine& e) { this->postSceneUpdate(e); });
auto tmp_lock = _func_handles[1].lock();
tmp_lock->priority = -1;
tmp_lock->name = "DefaultService::postSceneUpdate";
}
{
_func_handles[2] = engine.addFixedUpdate([this](Engine& e) { this->preSceneFixedUpdate(e); });
auto tmp_lock = _func_handles[2].lock();
tmp_lock->priority = 1;
tmp_lock->name = "DefaultService::preSceneFixedUpdate";
}
{
_func_handles[3] = engine.addFixedUpdate([this](Engine& e) { this->postSceneFixedUpdate(e); });
auto tmp_lock = _func_handles[3].lock();
tmp_lock->priority = -1;
tmp_lock->name = "DefaultService::postSceneFixedUpdate";
}
return true;
}
void DefaultService::disable(Engine& engine) {
engine.removeUpdate(_func_handles[0]);
engine.removeUpdate(_func_handles[1]);
engine.removeFixedUpdate(_func_handles[2]);
engine.removeFixedUpdate(_func_handles[3]);
_func_handles[0].reset();
_func_handles[1].reset();
_func_handles[2].reset();
_func_handles[3].reset();
}
} // MM::Services

View File

@ -0,0 +1,26 @@
#pragma once
#include "./service.hpp"
#include <mm/engine.hpp>
namespace MM::Services {
class DefaultService : public Service {
private:
Engine::FunctionDataHandle _func_handles[4];
public:
virtual const char* name(void) override { return "DefaultService"; }
virtual bool enable(Engine& engine) override;
virtual void disable(Engine& engine) override;
virtual void preSceneUpdate(Engine&) {}
virtual void postSceneUpdate(Engine&) {}
virtual void preSceneFixedUpdate(Engine&) {}
virtual void postSceneFixedUpdate(Engine&) {}
};
} //MM::Services

View File

@ -0,0 +1,77 @@
#pragma once
#include <mm/engine.hpp>
#include <vector>
#include <set>
namespace MM::Services {
class NetChanneledInterface : public Service {
public:
using channel_id = uint8_t;
enum class channel_type {
LOSSY,
LOSSLESS
};
using peer_id = uint64_t; // might be truncated by backends
using packet_type = uint16_t; // can be aliased to an enum by the user
protected:
std::set<peer_id> _peer_list;
public: // support querrys
// returns the number (highest number??) maximum channels supported by backend
virtual channel_id getMaxChannels(void) = 0;
// returns wether the channel type is supported
virtual bool getSupportedChannelType(channel_type type) = 0;
virtual size_t getMaxPacketSize(void) = 0;
// TODO: add set channel type utils
public:
// ok , we manage the peer list now
virtual bool addPeer(peer_id peer) { return _peer_list.emplace(peer).second; }
virtual bool removePeer(peer_id peer) { return _peer_list.erase(peer); }
virtual void clearPeerlist(void) { _peer_list.clear(); }
// calls fn for each peer
virtual void forEachPeer(std::function<void(peer_id)> fn) {
for (peer_id peer : _peer_list) {
fn(peer);
}
}
public: // send/recv
// sends a packet of max getMaxPacketSize() bytes
virtual bool sendPacket(peer_id peer, channel_id channel, uint8_t* data, size_t data_size) = 0;
// sends a packet, automatically split if too big
// !! only on lossless channels
virtual bool sendPacketLarge(peer_id peer, channel_id channel, uint8_t* data, size_t data_size) = 0;
// TODO: broadcast?
// has any?
//virtual bool getPacket
// calls fn for each packet and fills in peer, channel, data_ptr, and data_size
// returns number of fn calls
virtual size_t forEachPacket(std::function<void(peer_id, channel_id, uint8_t*, size_t)> fn) = 0;
// calls fn for each packet and fills in channel, data_ptr, and data_size
// returns number of fn calls
virtual size_t forEachPacketPeer(peer_id peer, std::function<void(peer_id, channel_id, uint8_t*, size_t)> fn) = 0;
// calls fn for each packet and fills in data_ptr, and data_size
// returns number of fn calls
virtual size_t forEachPacketPeerChannel(peer_id peer, channel_id channel, std::function<void(peer_id, channel_id, uint8_t*, size_t)> fn) = 0;
};
} // MM::Services

View File

@ -0,0 +1,27 @@
#include "./scene_service_interface.hpp"
#include <entt/entity/registry.hpp>
namespace MM {
struct DefaultSystemsContainer {
// list of callables, expecting a Scene (ecs) and a step size (delta)
std::vector<std::function<void(Scene&, float)>> systems;
};
void AddSystemToScene(::MM::Scene& scene, ::MM::System fn) {
auto& sc = scene.ctx_or_set<DefaultSystemsContainer>();
sc.systems.emplace_back(std::move(fn));
}
void EachSystemInScene(::MM::Scene& scene, std::function<void(::MM::Scene&, ::MM::System&)> fn) {
auto* sc = scene.try_ctx<DefaultSystemsContainer>();
if (sc != nullptr) {
for (auto& system : sc->systems) {
fn(scene, system);
}
}
}
} // MM

View File

@ -0,0 +1,44 @@
#pragma once
#include <mm/engine.hpp>
#include <entt/entity/fwd.hpp>
namespace MM {
using Entity = entt::entity;
using Scene = entt::basic_registry<::MM::Entity>;
using System = std::function<void(::MM::Scene&, float)>;
// opaque way to add a System to a Scene
void AddSystemToScene(::MM::Scene& scene, ::MM::System fn);
// opaque way to iterate over the Systems
void EachSystemInScene(::MM::Scene& scene, std::function<void(::MM::Scene&, ::MM::System&)> fn);
} // MM
namespace MM::Services {
class SceneServiceInterface : public Service {
public: // Scene stuff
// get current Scene
virtual ::MM::Scene& getScene(void) = 0;
// enques a new Scene to be put in place
virtual void changeScene(std::unique_ptr<::MM::Scene> new_scene) = 0;
// sets the new Scene to be provided.
// dont use, except for when you know what you are doing!
// be carefull of that one (lol)
virtual void changeSceneNow(std::unique_ptr<::MM::Scene> new_scene) = 0;
// adds a System to current Scene.
// default impl. will use getScene() !
inline virtual void addSystemToScene(::MM::System fn) {
::MM::AddSystemToScene(getScene(), std::move(fn));
}
};
} // MM::Services

View File

@ -0,0 +1,24 @@
#pragma once
namespace MM {
class Engine;
namespace Services {
class Service {
public:
virtual ~Service(void) {}
virtual const char* name(void) { return "UnNamedService"; }
//virtual const char* name(void) = 0; // use this to find unnamed services
// required
virtual bool enable(Engine& engine) = 0;
virtual void disable(Engine& engine) = 0;
};
} // Services
} //MM

View File

@ -0,0 +1,16 @@
add_executable(engine_test
update_test.cpp
run_test.cpp
service_system_test.cpp
default_service_test.cpp
)
target_include_directories(engine_test PRIVATE ".")
target_link_libraries(engine_test
engine
gtest_main
)
add_test(NAME engine_test COMMAND engine_test)

View File

@ -0,0 +1,38 @@
#include <gtest/gtest.h>
#include <mm/engine.hpp>
#include <mm/services/default_service.hpp>
TEST(engine_default_service_system, add_en_dis) {
MM::Engine e;
e.addService<MM::Services::DefaultService>();
ASSERT_TRUE(e.enableService<MM::Services::DefaultService>());
{
auto* dss = e.tryService<MM::Services::DefaultService>();
ASSERT_NE(dss, nullptr);
}
e.disableService<MM::Services::DefaultService>();
}
TEST(engine_default_service_system, tick) {
MM::Engine e;
e.addService<MM::Services::DefaultService>();
ASSERT_TRUE(e.enableService<MM::Services::DefaultService>());
e.update();
e.fixedUpdate();
{
auto* dss = e.tryService<MM::Services::DefaultService>();
ASSERT_NE(dss, nullptr);
}
e.disableService<MM::Services::DefaultService>();
}

View File

@ -0,0 +1,61 @@
#include <gtest/gtest.h>
#include <mm/engine.hpp>
TEST(engine_run, test_run) {
MM::Engine engine;
bool run = false;
auto test_fun = [&run](auto& e) {
run = true;
e.stop();
};
auto handle = engine.addFixedUpdate(test_fun);
ASSERT_NE(handle.lock(), nullptr);
handle.lock()->priority = 1;
ASSERT_FALSE(run);
engine.run();
ASSERT_TRUE(run);
engine.removeFixedUpdate(handle);
}
TEST(engine_run, test_mult_run) {
MM::Engine engine;
bool run = false;
unsigned int fu_count = 0;
unsigned int u_count = 0;
const unsigned int f_to_do = 4;
auto test_f_fun = [&](auto& e) {
run = true;
fu_count++;
if (fu_count >= f_to_do)
e.stop();
};
auto handle_f = engine.addFixedUpdate(test_f_fun);
ASSERT_NE(handle_f.lock(), nullptr);
handle_f.lock()->priority = 1;
auto handle = engine.addUpdate([&u_count](auto&) { u_count++; });
ASSERT_NE(handle_f.lock(), nullptr);
handle.lock()->priority = 1;
ASSERT_FALSE(run);
engine.run();
ASSERT_TRUE(run);
EXPECT_GT(u_count, f_to_do) << "expected more update runs than fixed update runs...";
std::cout << "while performing " << f_to_do << " fixed updates, the engine did " << u_count << " updates.\n";
engine.removeFixedUpdate(handle_f);
}

View File

@ -0,0 +1,228 @@
#include <gtest/gtest.h>
#include <mm/engine.hpp>
class TestService1 : public MM::Services::Service {
public:
const char* name(void) override { return "TestService1"; }
bool enable(MM::Engine&) override { return true; }
void disable(MM::Engine&) override {}
};
class TestService2 : public MM::Services::Service {
public:
const char* name(void) override { return "TestService2"; }
bool enable(MM::Engine&) override { return true; }
void disable(MM::Engine&) override {}
};
class TestService2Derived : public TestService2 {
public:
const char* name(void) override { return "TestService2Derived"; }
bool enable(MM::Engine&) override { return true; }
void disable(MM::Engine&) override {}
};
TEST(engine_service, add) {
MM::Engine e;
e.addService<TestService1>();
auto* tss = e.tryService<TestService1>();
ASSERT_NE(tss, nullptr);
}
TEST(engine_service, get_fail) {
MM::Engine e;
auto* tss = e.tryService<TestService1>();
ASSERT_EQ(tss, nullptr);
}
#ifndef NDEBUG
TEST(engine_service, _fail) {
MM::Engine e;
e.addService<TestService1>();
ASSERT_DEATH({
e.addService<TestService1>(); // adding the same
}, "failed");
//auto* tss = e.tryService<TestService1>();
//ASSERT_EQ(tss, nullptr);
}
#endif
TEST(engine_service, add_int_en_dis) {
MM::Engine e;
{
auto& tss = e.addService<TestService1>();
ASSERT_TRUE(tss.enable(e));
}
{
auto* tss = e.tryService<TestService1>();
ASSERT_NE(tss, nullptr);
tss->disable(e);
}
}
TEST(engine_service, add_en_dis) {
MM::Engine e;
e.addService<TestService1>();
ASSERT_TRUE(e.enableService<TestService1>());
{
auto* tss = e.tryService<TestService1>();
ASSERT_NE(tss, nullptr);
}
e.disableService<TestService1>();
}
TEST(engine_service, add_en_dis_mult) {
MM::Engine e;
e.addService<TestService1>();
ASSERT_TRUE(e.enableService<TestService1>());
e.addService<TestService2>();
ASSERT_TRUE(e.enableService<TestService2>());
{
auto* tss = e.tryService<TestService1>();
ASSERT_NE(tss, nullptr);
}
{
auto* tss = e.tryService<TestService2>();
ASSERT_NE(tss, nullptr);
}
e.disableService<TestService1>();
e.disableService<TestService2>();
}
TEST(engine_service, provide) {
MM::Engine e;
e.addService<TestService2Derived>();
ASSERT_TRUE(e.enableService<TestService2Derived>());
bool r;
// "mark" TestService2Derived as provider for TestService2
r = e.provide<TestService2, TestService2Derived>(); ASSERT_TRUE(r);
// should not compile
//r = e.provide<TestService2Derived, TestService2>();
//r = e.provide<TestService2, TestService2>();
//r = e.provide<TestService2Derived, TestService2Derived>();
{
auto* tdss = e.tryService<TestService2Derived>();
ASSERT_NE(tdss, nullptr);
auto* iss = e.tryService<TestService2>();
ASSERT_NE(iss, nullptr);
// they are the same (ptr)
ASSERT_EQ(tdss, iss);
}
e.disableService<TestService2Derived>();
}
TEST(engine_service, type_ids) {
MM::Engine e;
auto tss1_id = e.type<TestService1>();
auto tss2_id = e.type<TestService2>();
auto tss2d_id = e.type<TestService2Derived>();
ASSERT_NE(tss1_id, tss2_id);
ASSERT_NE(tss1_id, tss2d_id);
ASSERT_NE(tss2_id, tss2d_id);
}
TEST(engine_service, add_en_dis_mult_type_ids) {
MM::Engine e;
auto tss1_id = e.type<TestService1>();
auto tss2_id = e.type<TestService2>();
ASSERT_NE(tss1_id, tss2_id);
e.addService<TestService1>();
ASSERT_TRUE(e.enableService(tss1_id));
e.addService<TestService2>();
ASSERT_TRUE(e.enableService(tss2_id));
{
auto* tss1 = e.tryService<TestService1>();
ASSERT_NE(tss1, nullptr);
auto* tss2 = e.tryService<TestService2>();
ASSERT_NE(tss2, nullptr);
ASSERT_NE((void*)tss1, (void*)tss2);
}
e.disableService(tss1_id);
e.disableService<TestService2>(); // mixin'
}
TEST(engine_service, provide_type_ids) {
MM::Engine e;
auto tss2_id = e.type<TestService2>();
auto tss2d_id = e.type<TestService2Derived>();
e.addService<TestService2Derived>();
ASSERT_TRUE(e.enableService(tss2d_id));
bool r;
// "mark" TestService2Derived as provider for TestService2
r = e.provide(tss2_id, tss2d_id); ASSERT_TRUE(r); // typeid variant
//r = e.provide<TestService2, TestService2Derived>(); ASSERT_TRUE(r);
{
auto* tdss = e.tryService<TestService2Derived>();
ASSERT_NE(tdss, nullptr);
auto* iss = e.tryService<TestService2>();
ASSERT_NE(iss, nullptr);
// they are the same (ptr)
ASSERT_EQ((void*)tdss, (void*)iss);
}
e.disableService(tss2d_id);
}
TEST(engine_service, run) {
MM::Engine e;
e.addService<TestService1>();
ASSERT_TRUE(e.enableService<TestService1>());
{
auto* tss = e.tryService<TestService1>();
ASSERT_NE(tss, nullptr);
}
e.addFixedUpdate([](auto& e) { e.stop(); });
e.run();
e.disableService<TestService1>();
}

View File

@ -0,0 +1,230 @@
#include <gtest/gtest.h>
#include <mm/engine.hpp>
TEST(engine_fixed_update, empty_add_rm) {
MM::Engine engine;
auto test_fun = [](auto&) {};
auto handle = engine.addFixedUpdate(test_fun);
ASSERT_NE(handle.lock(), nullptr);
handle.lock()->priority = 1;
engine.removeFixedUpdate(handle);
}
TEST(engine_update, empty_add_rm) {
MM::Engine engine;
auto test_fun = [](auto&) {};
auto handle = engine.addUpdate(test_fun);
ASSERT_NE(handle.lock(), nullptr);
handle.lock()->priority = 1;
engine.removeUpdate(handle);
}
TEST(engine_fixed_update, empty_run) {
MM::Engine engine;
auto test_fun = [](auto&) {};
auto handle = engine.addFixedUpdate(test_fun);
ASSERT_NE(handle.lock(), nullptr);
handle.lock()->priority = 1;
engine.fixedUpdate(); // single update
engine.removeFixedUpdate(handle);
}
TEST(engine_update, empty_run) {
MM::Engine engine;
auto test_fun = [](auto&) {};
auto handle = engine.addUpdate(test_fun);
ASSERT_NE(handle.lock(), nullptr);
handle.lock()->priority = 1;
engine.update();
engine.removeUpdate(handle);
}
TEST(engine_fixed_update, test_run) {
MM::Engine engine;
bool run = false;
auto test_fun = [&run](auto&) { run = true; };
auto handle = engine.addFixedUpdate(test_fun);
ASSERT_NE(handle.lock(), nullptr);
handle.lock()->priority = 1;
ASSERT_FALSE(run);
engine.fixedUpdate(); // single update
ASSERT_TRUE(run);
engine.removeFixedUpdate(handle);
}
TEST(engine_update, test_run) {
MM::Engine engine;
bool run = false;
auto test_fun = [&run](auto&) { run = true; };
auto handle = engine.addUpdate(test_fun);
ASSERT_NE(handle.lock(), nullptr);
handle.lock()->priority = 1;
ASSERT_FALSE(run);
engine.update();
ASSERT_TRUE(run);
engine.removeUpdate(handle);
}
TEST(engine_fixed_update, test_order_run) {
MM::Engine engine;
bool run1 = false;
bool run2 = false;
auto test_fun1 = [&](auto&) {
ASSERT_FALSE(run2);
run1 = true;
};
auto test_fun2 = [&](auto&) {
ASSERT_TRUE(run1);
run2 = true;
};
auto handle1 = engine.addFixedUpdate(test_fun1);
ASSERT_NE(handle1.lock(), nullptr);
handle1.lock()->priority = 1;
auto handle2 = engine.addFixedUpdate(test_fun2);
ASSERT_NE(handle2.lock(), nullptr);
handle2.lock()->priority = 0;
ASSERT_FALSE(run1);
ASSERT_FALSE(run2);
engine.fixedUpdate(); // single update
ASSERT_TRUE(run1);
ASSERT_TRUE(run2);
engine.removeFixedUpdate(handle1);
engine.removeFixedUpdate(handle2);
}
TEST(engine_fixed_update, test_order_rev_run) {
MM::Engine engine;
bool run1 = false;
bool run2 = false;
auto test_fun1 = [&](auto&) {
ASSERT_TRUE(run2);
run1 = true;
};
auto test_fun2 = [&](auto&) {
ASSERT_FALSE(run1);
run2 = true;
};
auto handle1 = engine.addFixedUpdate(test_fun1);
ASSERT_NE(handle1.lock(), nullptr);
handle1.lock()->priority = 0;
auto handle2 = engine.addFixedUpdate(test_fun2);
ASSERT_NE(handle2.lock(), nullptr);
handle2.lock()->priority = 1;
ASSERT_FALSE(run1);
ASSERT_FALSE(run2);
engine.fixedUpdate(); // single update
ASSERT_TRUE(run1);
ASSERT_TRUE(run2);
engine.removeFixedUpdate(handle1);
engine.removeFixedUpdate(handle2);
}
TEST(engine_update, test_order_run) {
MM::Engine engine;
bool run1 = false;
bool run2 = false;
auto test_fun1 = [&](auto&) {
ASSERT_FALSE(run2);
run1 = true;
};
auto test_fun2 = [&](auto&) {
ASSERT_TRUE(run1);
run2 = true;
};
auto handle1 = engine.addUpdate(test_fun1);
ASSERT_NE(handle1.lock(), nullptr);
handle1.lock()->priority = 1;
auto handle2 = engine.addUpdate(test_fun2);
ASSERT_NE(handle2.lock(), nullptr);
handle2.lock()->priority = 0;
ASSERT_FALSE(run1);
ASSERT_FALSE(run2);
engine.update(); // single update
ASSERT_TRUE(run1);
ASSERT_TRUE(run2);
engine.removeUpdate(handle1);
engine.removeUpdate(handle2);
}
TEST(engine_update, test_order_rev_run) {
MM::Engine engine;
bool run1 = false;
bool run2 = false;
auto test_fun1 = [&](auto&) {
ASSERT_TRUE(run2);
run1 = true;
};
auto test_fun2 = [&](auto&) {
ASSERT_FALSE(run1);
run2 = true;
};
auto handle1 = engine.addUpdate(test_fun1);
ASSERT_NE(handle1.lock(), nullptr);
handle1.lock()->priority = 0;
auto handle2 = engine.addUpdate(test_fun2);
ASSERT_NE(handle2.lock(), nullptr);
handle2.lock()->priority = 1;
ASSERT_FALSE(run1);
ASSERT_FALSE(run2);
engine.update(); // single update
ASSERT_TRUE(run1);
ASSERT_TRUE(run2);
engine.removeUpdate(handle1);
engine.removeUpdate(handle2);
}

View File

@ -0,0 +1,45 @@
cmake_minimum_required(VERSION 3.2)
project(filesystem_service CXX)
add_library(filesystem_service
src/mm/path_utils.hpp
src/mm/services/filesystem.hpp
src/mm/services/filesystem.cpp
src/mm/fs_const_archiver.hpp
src/mm/fs_const_archiver.cpp
)
target_include_directories(filesystem_service PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/src")
target_link_libraries(filesystem_service
engine
logger
entt
nlohmann_json::nlohmann_json
physfs-static # TODO: fix this
std_utils
)
if(NOT MM_HEADLESS)
#if android
#target_link_libraries(filesystem_service SDL)
#endif
if(EMSCRIPTEN)
set_target_properties(filesystem_service PROPERTIES COMPILE_FLAGS "-s USE_SDL=2")
set_target_properties(filesystem_service PROPERTIES LINK_FLAGS "-s USE_SDL=2")
else()
#if not android or emscripten
target_include_directories(filesystem_service PUBLIC "${SDL2_INCLUDE_DIR}")
target_link_libraries(filesystem_service ${SDL2_LIBRARY})
#endif
endif()
endif()
if (BUILD_TESTING)
add_subdirectory(test)
endif()

View File

@ -0,0 +1,247 @@
#include "./fs_const_archiver.hpp"
#include <cstring>
#include <mm/logger.hpp>
#define LOG_CRIT(...) __LOG_CRIT( "Filesystem", __VA_ARGS__)
#define LOG_ERROR(...) __LOG_ERROR("Filesystem", __VA_ARGS__)
#define LOG_WARN(...) __LOG_WARN( "Filesystem", __VA_ARGS__)
#define LOG_INFO(...) __LOG_INFO( "Filesystem", __VA_ARGS__)
#define LOG_DEBUG(...) __LOG_DEBUG("Filesystem", __VA_ARGS__)
#define LOG_TRACE(...) __LOG_TRACE("Filesystem", __VA_ARGS__)
namespace MM {
std::unordered_map<std::string, std::pair<uint8_t*, size_t>> FSConstArchiver::_storage {};
void* FSConstArchiver::PH_openArchive(PHYSFS_Io* io, const char* name, int forWrite, int* claimed) {
(void)io;
(void)name;
// TODO: test name
if (forWrite) {
//PHYSFS_setErrorCode(PHYSFS_ERR_READ_ONLY);
*claimed = 0;
return nullptr;
}
*claimed = 1;
return (void*)1; // non zero
}
PHYSFS_EnumerateCallbackResult FSConstArchiver::PH_enumerate(void* opaque, const char* dirname, PHYSFS_EnumerateCallback cb, const char* origdir, void* callbackdata) {
(void)opaque;
(void)dirname;
(void)cb;
(void)origdir;
(void)callbackdata;
// "files" just not enumeratable
//return PHYSFS_ENUM_STOP;
return PHYSFS_ENUM_OK;
}
PHYSFS_Io* FSConstArchiver::PH_openRead(void* opaque, const char* fnm) {
(void)opaque;
if (!_storage.count(fnm)) {
PHYSFS_setErrorCode(PHYSFS_ERR_NOT_FOUND);
return nullptr;
}
return createIO(fnm);
}
PHYSFS_Io* FSConstArchiver::PH_openWrite(void* opaque, const char* filename) {
(void)opaque;
(void)filename;
return nullptr;
}
PHYSFS_Io* FSConstArchiver::PH_openAppend(void* opaque, const char* filename) {
(void)opaque;
(void)filename;
return nullptr;
}
int FSConstArchiver::PH_remove(void* opaque, const char* filename) {
(void)opaque;
(void)filename;
// TODO: support this?
PHYSFS_setErrorCode(PHYSFS_ERR_READ_ONLY);
return 0;
}
int FSConstArchiver::PH_mkdir(void* opaque, const char* filename) {
(void)opaque;
(void)filename;
PHYSFS_setErrorCode(PHYSFS_ERR_READ_ONLY);
return 0;
}
int FSConstArchiver::PH_stat(void* opaque, const char* fn, PHYSFS_Stat* stat) {
(void)opaque;
if (pathIsDir(fn)) {
stat->filesize = 0;
stat->filetype = PHYSFS_FILETYPE_DIRECTORY;
} else if (_storage.count(fn)) {
stat->filesize = _storage[fn].second;
stat->filetype = PHYSFS_FILETYPE_REGULAR;
} else {
return 0;
}
stat->accesstime = -1;
stat->createtime = -1;
stat->modtime = -1;
stat->readonly = 1;
return 1;
}
void FSConstArchiver::PH_closeArchive(void* opaque) {
(void)opaque;
}
PHYSFS_Io* FSConstArchiver::createIO(const char* filename, PHYSFS_uint64 pos) {
if (!_storage.count(filename)) {
//LOGCA(std::string("error: path '") + filename + "' alredy used!");
LOG_ERROR("path '{}' already used!", filename);
return nullptr;
}
//LOGCA(std::string("###creating io for ") + filename);
LOG_TRACE("creating io for {}", filename);
struct io_internal {
std::string filename;
PHYSFS_uint64 pos = 0;
uint8_t* data = nullptr;
size_t data_size = 0;
};
auto io = new PHYSFS_Io;
io->version = 0;
io->write = nullptr;
io->flush = nullptr;
io->opaque = new io_internal{
filename,
pos,
_storage[filename].first,
_storage[filename].second
};
io->destroy = [](PHYSFS_Io* io) {
delete (io_internal*)io->opaque;
delete io;
};
io->tell = [](PHYSFS_Io* io) -> PHYSFS_sint64 { return ((io_internal*)io->opaque)->pos; };
io->seek = [](PHYSFS_Io* io, PHYSFS_uint64 offset) -> int {
auto* inter = (io_internal*)io->opaque;
if (offset > inter->data_size)
return 0; // error, past end
inter->pos = offset;
return 1;
};
io->length = [](PHYSFS_Io* io) -> PHYSFS_sint64 { return ((io_internal*)io->opaque)->data_size; };
io->duplicate = [](PHYSFS_Io* io) -> PHYSFS_Io* {
if (!io)
return nullptr;
auto* inter = (io_internal*)io->opaque;
auto* dup = createIO(inter->filename.c_str(), inter->pos);
return dup;
};
io->read = [](PHYSFS_Io* io, void* buf, PHYSFS_uint64 len) -> PHYSFS_sint64 {
if (!io)
return -1;
auto* inter = (io_internal*)io->opaque;
if (inter->data_size == inter->pos)
return 0; // EOF
PHYSFS_sint64 bytes_to_read = 0;
if (inter->pos + len >= inter->data_size) {
bytes_to_read = inter->data_size - inter->pos; // remaining data
} else {
bytes_to_read = len;
}
memcpy(buf, inter->data + inter->pos, bytes_to_read);
inter->pos += bytes_to_read;
return bytes_to_read;
};
return io;
}
bool FSConstArchiver::pathIsDir(const char* path) {
std::string_view pstr {path};
//for (auto&[str, data] : _storage) {
for (auto& it : _storage) {
// stats_with c++20 <.<
if (it.first.compare(0, pstr.size(), pstr) == 0) {
return true;
}
}
return false;
}
const PHYSFS_Archiver* FSConstArchiver::getArchiverStruct(void) {
static const PHYSFS_Archiver a {
0, // version
{ // info
"MEM", // ext
"in const memory 'archiver'", // desc
"Green", // author
"", // url
0 // sym
},
&PH_openArchive,
&PH_enumerate,
&PH_openRead,
&PH_openWrite,
&PH_openAppend,
&PH_remove,
&PH_mkdir,
&PH_stat,
&PH_closeArchive
};
return &a;
}
void FSConstArchiver::addFile(const char* path, uint8_t* data, size_t data_size) {
if (!path)
return;
// remove leading / es
while (path[0] == '/') {
path++;
if (path[0] == '\0')
return;
}
if (_storage.count(path)) {
//LOGCA(std::string("error: '") + path + "' already in path");
LOG_TRACE("'{}' already in path, overriding...", path);
}
_storage[path] = {data, data_size};
}
} // MM

View File

@ -0,0 +1,42 @@
#pragma once
#include <mm/services/filesystem.hpp>
#include <physfs.h>
#include <unordered_map>
namespace MM {
class FSConstArchiver {
private:
static std::unordered_map<std::string, std::pair<uint8_t*, size_t>> _storage;
private:
// archiver interface
static void* PH_openArchive(PHYSFS_Io* io, const char* name, int forWrite, int* claimed);
static PHYSFS_EnumerateCallbackResult PH_enumerate(void* opaque, const char* dirname, PHYSFS_EnumerateCallback cb, const char* origdir, void* callbackdata);
static PHYSFS_Io* PH_openRead(void* opaque, const char* fnm);
static PHYSFS_Io* PH_openWrite(void* opaque, const char* filename);
static PHYSFS_Io* PH_openAppend(void* opaque, const char* filename);
static int PH_remove(void* opaque, const char* filename);
static int PH_mkdir(void* opaque, const char* filename);
static int PH_stat(void* opaque, const char* fn, PHYSFS_Stat* stat);
static void PH_closeArchive(void* opaque);
static PHYSFS_Io* createIO(const char* filename, PHYSFS_uint64 pos = 0);
static bool pathIsDir(const char* path);
public:
static const PHYSFS_Archiver* getArchiverStruct(void);
// the archiver is not responsible for memory, you need to keep it around.
static void addFile(const char* path, uint8_t* data, size_t data_size);
};
} // MM
#define FS_CONST_MOUNT_FILE(path, x) MM::FSConstArchiver::addFile(path, (uint8_t*)x, sizeof x);
#define FS_CONST_MOUNT_FILE_S(path, x, size) MM::FSConstArchiver::addFile(path, x, size);
//#define FS_CONST_MOUNT_FILE_STATIC(path, x) struct __internal_fs_const_struct_t { __internal_fs_const_struct_t(void) { FS_CONST_MOUNT_FILE(path, x) } }; static __internal_fs_const_struct_t __internal_fs_const_struct {};

View File

@ -0,0 +1,49 @@
#pragma once
// TODO: test
#include <mm/string_view_split.hpp>
// TODO: make proper namespace
namespace MM {
// removes './'s and resolves '../'
// returns false if too many ..
inline bool path_shorten(std::string& path) {
auto splits = std_utils::split(path, "/");
std::vector<std::string_view> new_splits;
for (size_t i = 0; i < splits.size(); i++) {
if (splits[i] == "..") {
if (new_splits.empty())
return false;
new_splits.pop_back();
} else if (splits[i] == ".") {
// skip
} else {
new_splits.emplace_back(splits[i]);
}
}
std::string new_path;
for (auto& s : new_splits) {
new_path += "/";
new_path += s;
}
path = new_path;
return true;
}
// remove the file name from the path
inline std::string base_path(std::string_view path) {
auto pos = path.find_last_of("/");
if (pos == path.npos)
return std::string(path); // TODO: make sure there is a '/'
return std::string(path.substr(0, pos+1));
}
} // MM

View File

@ -0,0 +1,354 @@
#include "./filesystem.hpp"
#include "../fs_const_archiver.hpp"
#include <physfs.h>
#include <mm/path_utils.hpp>
#ifndef MM_HEADLESS
#include <SDL.h>
#endif
#include <nlohmann/json.hpp>
#include <mm/logger.hpp>
#define LOG_CRIT(...) __LOG_CRIT( "Filesystem", __VA_ARGS__)
#define LOG_ERROR(...) __LOG_ERROR("Filesystem", __VA_ARGS__)
#define LOG_WARN(...) __LOG_WARN( "Filesystem", __VA_ARGS__)
#define LOG_INFO(...) __LOG_INFO( "Filesystem", __VA_ARGS__)
#define LOG_DEBUG(...) __LOG_DEBUG("Filesystem", __VA_ARGS__)
#define LOG_TRACE(...) __LOG_TRACE("Filesystem", __VA_ARGS__)
namespace MM::Services {
bool FilesystemService::enable(Engine&) {
if (PHYSFS_isInit()) {
LOG_ERROR("physfs already initialized!!");
return false;
}
if (!PHYSFS_init(_argv0)) {
LOG_ERROR("error initializing physfs: {}", PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode()));
return false;
}
#if !defined(MM_HEADLESS) && !defined(EMSCRIPTEN)
char* pref_path = SDL_GetPrefPath("made_of_jelly", _app_name);
#else
// org only respected on windows
const char* pref_path = PHYSFS_getPrefDir("made_of_jelly", _app_name); // TODO: make this point to the right dir
#endif
if (!pref_path) {
LOG_ERROR("error getting pref path");
return false; // TODO: defaulting to base path?
} else {
if (!PHYSFS_setWriteDir(pref_path)) {
LOG_ERROR("error setting physfs write dir: {}", PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode()));
//return false;
}
//LOGFS(std::string("write dir set to: ") + PHYSFS_getWriteDir());
LOG_INFO("write dir set to: {}", PHYSFS_getWriteDir());
if (!PHYSFS_mount(pref_path, NULL, 0)) {
LOG_ERROR("mounting physfs write dir: {}", PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode()));
//return false;
}
#if !defined(MM_HEADLESS) && !defined(EMSCRIPTEN)
SDL_free(pref_path);
#endif
}
// add base path to search tree
if (_try_mount_base) {
if (!PHYSFS_mount(PHYSFS_getBaseDir(), NULL, 0)) {
LOG_ERROR("mounting physfs base dir: {}", PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode()));
}
}
// mount self (exec) :P
if (_try_mount_self) {
if (PHYSFS_mount(_argv0, "/", 1)) {
LOG_INFO("mounted self!!");
}
}
// const archiver
if (!PHYSFS_registerArchiver(FSConstArchiver::getArchiverStruct())) {
LOG_ERROR("error registering const archiver: {}", PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode()));
}
// fake mount, to add mount const archiver
if (!PHYSFS_mountMemory((void*)1, 1, nullptr, "main.mem", "/", 1)) { // should trigger
LOG_ERROR("error mounting const archiver: {}", PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode()));
}
for (const auto&[archive, mount_point, append] : _try_mount_list) {
if (!PHYSFS_mount(archive.c_str(), mount_point.c_str(), append?1:0)) {
LOG_ERROR("mounting physfs userdefined archive: {}", PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode()));
}
}
return true;
}
void FilesystemService::disable(Engine&) {
if (!PHYSFS_deinit()) {
LOG_ERROR("error deinitializing physfs: {}", PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode()));
}
}
FilesystemService::FilesystemService(void) {
MM::Logger::initSectionLogger("Filesystem");
}
FilesystemService::FilesystemService(
const char* argv0,
const char* app_name,
const bool try_mount_self,
const bool try_mount_base,
const std::vector<std::tuple<std::string, std::string, bool>>& try_mount_list
) : _argv0(argv0), _app_name(app_name), _try_mount_self(try_mount_self), _try_mount_base(try_mount_base) {
MM::Logger::initSectionLogger("Filesystem");
//_try_mount_list = try_mount_list;
for (const auto& it : try_mount_list) {
_try_mount_list.emplace_back(it);
}
}
FilesystemService::~FilesystemService(void) {
}
bool FilesystemService::exists(const char* filepath) const {
return PHYSFS_exists(filepath);
}
bool FilesystemService::isFile(const char* filepath) const {
if (!PHYSFS_exists(filepath)) {
return false;
}
PHYSFS_Stat stat;
if (PHYSFS_stat(filepath, &stat) == 0) {
return false;
}
// TODO: y other tho
return stat.filetype == PHYSFS_FileType::PHYSFS_FILETYPE_REGULAR || stat.filetype == PHYSFS_FileType::PHYSFS_FILETYPE_OTHER;
}
bool FilesystemService::isDir(const char* filepath) const {
if (!PHYSFS_exists(filepath)) {
return false;
}
PHYSFS_Stat stat;
if (PHYSFS_stat(filepath, &stat) == 0) {
return false;
}
return stat.filetype == PHYSFS_FileType::PHYSFS_FILETYPE_DIRECTORY;
}
void FilesystemService::forEachIn(const char* filepath, std::function<bool(const char*)> fn) const {
char** rc = PHYSFS_enumerateFiles(filepath);
for (char** i = rc; *i != NULL; i++) {
if (!fn(*i)) {
break;
}
}
PHYSFS_freeList(rc);
}
FilesystemService::fs_file_t FilesystemService::open(const char* filepath, FilesystemService::FOPEN_t t) const {
if (!filepath) {
return nullptr;
}
PHYSFS_File* phys_file = nullptr;
switch (t) {
case FOPEN_t::READ:
LOG_TRACE("opening '{}' in read mode", filepath);
phys_file = PHYSFS_openRead(filepath);
break;
case FOPEN_t::WRITE:
LOG_TRACE("opening '{}' in write mode", filepath);
if (std::string_view(filepath).find('/') != std::string_view::npos) {// bc dirs might not exist
auto tmp_dir_path = MM::base_path(filepath);
PHYSFS_mkdir(tmp_dir_path.c_str());
}
phys_file = PHYSFS_openWrite(filepath);
break;
case FOPEN_t::APPEND:
LOG_TRACE("opening '{}' in append mode", filepath);
if (std::string_view(filepath).find('/') != std::string_view::npos) {// bc dirs might not exist
auto tmp_dir_path = MM::base_path(filepath);
PHYSFS_mkdir(tmp_dir_path.c_str());
}
phys_file = PHYSFS_openAppend(filepath);
break;
default:
LOG_ERROR("invalid fopen mode {} for '{}'", t, filepath);
return nullptr;
}
if (!phys_file) {
LOG_ERROR("error while opening '{}' : {}", filepath, PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode()));
}
return phys_file;
}
bool FilesystemService::close(fs_file_t file) const {
if (!PHYSFS_close(file)) {
//LOGFS_physfs("error while closing file ");
// ignore. windoof for some reason allways fails with "permission denied" (for closing read handles)
LOG_TRACE("error while closing file : {}", PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode()));
return false;
}
return true;
}
bool FilesystemService::eof(fs_file_t file) const {
return PHYSFS_eof(file);
}
int64_t FilesystemService::tell(fs_file_t file) const {
int64_t ret = PHYSFS_tell(file);
if (ret < 0) {
LOG_ERROR("error while determining file position (tell()) {}", PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode()));
}
return ret;
}
bool FilesystemService::seek(fs_file_t file, uint64_t pos) const {
if (!PHYSFS_seek(file, pos)) {
LOG_ERROR("error while seeking in file: {}", PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode()));
return false;
}
return true;
}
int64_t FilesystemService::read(fs_file_t file, void* buffer, uint64_t size) const {
auto r = PHYSFS_readBytes(file, buffer, size);
if (r < 0) {
LOG_ERROR("error while reading file: {}", PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode()));
}
return r;
}
uint64_t FilesystemService::write(fs_file_t file, const void* buffer, uint64_t size) const {
uint64_t r = static_cast<uint64_t>(PHYSFS_writeBytes(file, buffer, size));
// TODO: handle partial writes differently
if (r != size) {
LOG_ERROR("error while writing file: {}", PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode()));
}
return r;
}
void FilesystemService::flush(fs_file_t file) const {
if (!PHYSFS_flush(file)) {
LOG_ERROR("error flushing ..?: {}", PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode()));
}
}
int64_t FilesystemService::length(fs_file_t file) const {
return PHYSFS_fileLength(file);
}
int64_t FilesystemService::readString(fs_file_t file, std::string& string) const {
char buffer[256] = {};
int64_t read_bytes = 0;
while (int64_t r = read(file, buffer, 256)) {
if (r < 0) {
return r;
}
string.append(buffer, static_cast<size_t>(r));
read_bytes += r;
}
return read_bytes;
}
nlohmann::json FilesystemService::readJson(fs_file_t file) const {
if (!file)
return {};
seek(file, 0);
std::string buffer;
readString(file, buffer);
return nlohmann::json::parse(buffer);
}
nlohmann::json FilesystemService::readJson(const char* filepath) const {
if (!exists(filepath)) {
return {};
}
auto h = open(filepath, READ);
auto r = readJson(h);
close(h);
return r;
}
bool FilesystemService::writeJson(fs_file_t file, nlohmann::json& j, const int indent, const char indent_char) const {
if (!file) {
LOG_ERROR("writing json to invalid file");
return false;
}
auto s = j.dump(indent, indent_char);
auto r = write(file, s.c_str(), s.size());
return r == s.size();
}
bool FilesystemService::writeJson(const char* filepath, nlohmann::json& j, const int indent, const char indent_char) const {
auto h = open(filepath, WRITE);
auto r = writeJson(h, j, indent, indent_char);
close(h);
return r;
}
bool FilesystemService::writeJson(fs_file_t file, nlohmann::ordered_json& j, const int indent, const char indent_char) const {
if (!file) {
LOG_ERROR("writing json to invalid file");
return false;
}
auto s = j.dump(indent, indent_char);
auto r = write(file, s.c_str(), s.size());
return r == s.size();
}
bool FilesystemService::writeJson(const char* filepath, nlohmann::ordered_json& j, const int indent, const char indent_char) const {
auto h = open(filepath, WRITE);
auto r = writeJson(h, j, indent, indent_char);
close(h);
return r;
}
} // MM::Services

View File

@ -0,0 +1,89 @@
#pragma once
#include <cstdint>
#include <functional>
//#include <physfs.h>
//fwd
typedef struct PHYSFS_File PHYSFS_File;
typedef struct PHYSFS_Stat PHYSFS_Stat;
#include <nlohmann/json_fwd.hpp>
#include <mm/engine.hpp>
namespace MM::Services {
class FilesystemService : public Service {
public:
using fs_file_t = PHYSFS_File*;
public:
bool enable(Engine&) override;
void disable(Engine&) override;
const char* name(void) override { return "Filesystem"; }
private:
// setup data
const char* _argv0 = nullptr;
const char* _app_name = "default_app";
const bool _try_mount_self = true;
const bool _try_mount_base = true;
// TODO: write dir
std::vector<std::tuple<std::string, std::string, bool>> _try_mount_list;
public:
FilesystemService(void);
FilesystemService(
const char* argv0 = nullptr,
const char* app_name = "default_app",
const bool try_mount_self = true,
const bool try_mount_base = true,
const std::vector<std::tuple<std::string, std::string, bool>>& try_mount_list = {}
);
~FilesystemService(void);
bool exists(const char* filepath) const;
bool isFile(const char* filepath) const;
bool isDir(const char* filepath) const;
void forEachIn(const char* filepath, std::function<bool(const char*)> fn) const;
enum FOPEN_t { READ, WRITE, APPEND };
// opens file, expects path and mode
fs_file_t open(const char* filepath, FOPEN_t = READ) const;
bool close(fs_file_t file) const;
bool eof(fs_file_t file) const;
int64_t tell(fs_file_t file) const;
bool seek(fs_file_t file, uint64_t pos) const;
// read from file into buffer max size bytes, returns read bytes
int64_t read(fs_file_t file, void* buffer, uint64_t size) const;
// writes from buffer to file, returns written bytes
uint64_t write(fs_file_t file, const void* buffer, uint64_t size) const;
void flush(fs_file_t file) const;
int64_t length(fs_file_t file) const;
// reads in (remaining) file to string, and returns read bytes
int64_t readString(fs_file_t file, std::string& string) const;
// json
nlohmann::json readJson(fs_file_t file) const;
nlohmann::json readJson(const char* filepath) const;
bool writeJson(fs_file_t file, nlohmann::json& j, const int indent = -1, const char indent_char = ' ') const;
bool writeJson(const char* filepath, nlohmann::json& j, const int indent = -1, const char indent_char = ' ') const;
bool writeJson(fs_file_t file, nlohmann::ordered_json& j, const int indent = -1, const char indent_char = ' ') const;
bool writeJson(const char* filepath, nlohmann::ordered_json& j, const int indent = -1, const char indent_char = ' ') const;
};
} // namespace MM::Services

View File

@ -0,0 +1,14 @@
add_executable(filesystem_service_test
filesystem_tests.cpp
res/test.zip.h
)
target_include_directories(filesystem_service_test PRIVATE ".")
target_link_libraries(filesystem_service_test
filesystem_service
gtest_main
)
add_test(NAME filesystem_service_test COMMAND filesystem_service_test)

View File

@ -0,0 +1,307 @@
#include <gtest/gtest.h>
#include <mm/services/filesystem.hpp>
//#include <physfs.h>
#include <nlohmann/json.hpp>
#include <mm/fs_const_archiver.hpp>
#include <mm/logger.hpp>
#include "res/test.zip.h" // zip in memory
char* argv0; // HACK
TEST(fs_service_system, general) {
MM::Engine engine;
engine.addService<MM::Services::FilesystemService>(argv0, "filesystem_Test");
ASSERT_TRUE(engine.enableService<MM::Services::FilesystemService>());
auto* fs_ss_ptr = engine.tryService<MM::Services::FilesystemService>();
ASSERT_NE(fs_ss_ptr, nullptr);
ASSERT_FALSE(fs_ss_ptr->open("i_do_not_exist.txt"));
ASSERT_FALSE(fs_ss_ptr->exists("test_file.txt"));
ASSERT_TRUE(PHYSFS_mountMemory(test_zip, test_zip_len, NULL, "", NULL, 0));
ASSERT_TRUE(fs_ss_ptr->exists("test_file.txt"));
{
MM::Services::FilesystemService::fs_file_t file = fs_ss_ptr->open("test_file.txt", MM::Services::FilesystemService::FOPEN_t::READ);
ASSERT_TRUE(file) << "File opened";
ASSERT_FALSE(fs_ss_ptr->eof(file));
ASSERT_EQ(fs_ss_ptr->tell(file), 0) << "position at start of file";
char buffer[9] = {0};
ASSERT_EQ(fs_ss_ptr->read(file, buffer, 8), 8) << "file length == 8";
ASSERT_STREQ(buffer, "test :D\n");
ASSERT_TRUE(fs_ss_ptr->eof(file));
ASSERT_EQ(fs_ss_ptr->tell(file), 8) << "position at end of file, in this case 8";
ASSERT_TRUE(fs_ss_ptr->close(file));
}
engine.disableService<MM::Services::FilesystemService>();
}
TEST(fs_service_system, fopen_eof_tell) {
MM::Engine engine;
engine.addService<MM::Services::FilesystemService>(argv0, "filesystem_Test");
ASSERT_TRUE(engine.enableService<MM::Services::FilesystemService>());
auto* fs_ss_ptr = engine.tryService<MM::Services::FilesystemService>();
ASSERT_NE(fs_ss_ptr, nullptr);
ASSERT_TRUE(PHYSFS_mountMemory(test_zip, test_zip_len, NULL, "", NULL, 0));
MM::Services::FilesystemService::fs_file_t file = fs_ss_ptr->open("test_file.txt", MM::Services::FilesystemService::FOPEN_t::READ);
ASSERT_TRUE(file) << "File opened";
auto& fs = *fs_ss_ptr;
ASSERT_FALSE(fs.eof(file));
ASSERT_EQ(fs.tell(file), 0) << "position at start of file";
ASSERT_TRUE(fs.seek(file, 3));
ASSERT_FALSE(fs.eof(file));
ASSERT_EQ(fs.tell(file), 3);
ASSERT_FALSE(fs.seek(file, 10)) << "seek past end";
ASSERT_FALSE(fs.eof(file));
ASSERT_EQ(fs.tell(file), 3);
ASSERT_TRUE(fs.close(file));
engine.disableService<MM::Services::FilesystemService>();
}
TEST(fs_service_system, fopen_w_a_write) {
MM::Engine engine;
engine.addService<MM::Services::FilesystemService>(argv0, "filesystem_Test");
ASSERT_TRUE(engine.enableService<MM::Services::FilesystemService>());
auto* fs_ss_ptr = engine.tryService<MM::Services::FilesystemService>();
ASSERT_NE(fs_ss_ptr, nullptr);
auto& fs = *fs_ss_ptr;
const char* filename = "test2_write_file.txt";
// write
{
if (fs.exists(filename)) {
PHYSFS_delete(filename); // TODO: add wrapper
std::cout << "######deleted existing file" << std::endl;
}
auto file = fs.open(filename, MM::Services::FilesystemService::FOPEN_t::WRITE);
ASSERT_TRUE(file) << "File opened";
//ASSERT_TRUE(MM::fs_service_system::eof(file)); actually false
ASSERT_EQ(fs.tell(file), 0) << "position at start of file";
ASSERT_TRUE(fs.seek(file, 10)) << "seek past end";
ASSERT_EQ(fs.tell(file), 10);
const char data[4] {'a', 's', 'd', 'f'};
ASSERT_EQ(fs.write(file, data, 4), 4);
ASSERT_EQ(fs.tell(file), 14);
fs.flush(file);
ASSERT_TRUE(fs.close(file));
}
// append
{
auto file = fs.open(filename, MM::Services::FilesystemService::FOPEN_t::APPEND);
ASSERT_TRUE(file) << "File opened";
//ASSERT_TRUE(MM::fs_service_system::eof(file)); actually false
ASSERT_EQ(fs.tell(file), 14) << "position at start of file";
const char data[4] {'j', 'k', 'l', 'o'};
ASSERT_EQ(fs.write(file, data, 4), 4);
ASSERT_EQ(fs.tell(file), 18);
fs.flush(file);
ASSERT_TRUE(fs.close(file));
}
// read
{
auto file = fs.open(filename, MM::Services::FilesystemService::FOPEN_t::READ);
ASSERT_TRUE(file) << "File opened";
ASSERT_FALSE(fs.eof(file));
ASSERT_EQ(fs.tell(file), 0) << "position at start of file";
// fail
{
const char data[4] {'j', 'k', 'l', 'o'};
ASSERT_FALSE(fs.write(file, data, 4) == 4);
}
ASSERT_TRUE(fs.seek(file, 10));
char buffer[9] = {0};
ASSERT_EQ(fs.read(file, buffer, 8), 8);
ASSERT_STREQ(buffer, "asdfjklo");
ASSERT_TRUE(fs.eof(file));
ASSERT_TRUE(fs.close(file));
}
engine.disableService<MM::Services::FilesystemService>();
}
// TODO: readString
// TODO: fwrite ?
TEST(fs_service_system, read_json) {
MM::Engine engine;
engine.addService<MM::Services::FilesystemService>(argv0, "filesystem_Test");
ASSERT_TRUE(engine.enableService<MM::Services::FilesystemService>());
auto* fs_ss_ptr = engine.tryService<MM::Services::FilesystemService>();
ASSERT_NE(fs_ss_ptr, nullptr);
auto& fs = *fs_ss_ptr;
{
auto j = fs.readJson("does_not_exist.json");
ASSERT_TRUE(j.empty());
}
ASSERT_TRUE(PHYSFS_mountMemory(test_zip, test_zip_len, NULL, "", NULL, 0));
{
auto j1 = fs.readJson("wall_concrete-1_se_0.2.json");
//SPDLOG_INFO("dump: \n{}", j1.dump(4));
ASSERT_FALSE(j1.empty());
ASSERT_EQ(j1["type"], "tileset");
}
engine.disableService<MM::Services::FilesystemService>();
}
TEST(fs_service_system, read_const_json) {
MM::Engine engine;
engine.addService<MM::Services::FilesystemService>(argv0, "filesystem_Test");
ASSERT_TRUE(engine.enableService<MM::Services::FilesystemService>());
auto* fs_ss_ptr = engine.tryService<MM::Services::FilesystemService>();
ASSERT_NE(fs_ss_ptr, nullptr);
auto& fs = *fs_ss_ptr;
{
auto j = fs.readJson("does_not_exist.json");
ASSERT_TRUE(j.empty());
}
FS_CONST_MOUNT_FILE("json/file1.json",
R"({
"answer": {
"everything": 42
},
"happy": true,
"list": [
1,
0,
2
],
"name": "Niels",
"nothing": null,
"object": {
"currency": "USD",
"value": 42.99
},
"pi": 3.141
})")
FS_CONST_MOUNT_FILE("/json/file2.json",
R"({
"answer": 42
})")
{
auto j1 = fs.readJson("/json/file1.json");
//std::cout << "dump: \n" << std::setw(4) << j1 << std::endl;
ASSERT_FALSE(j1.empty());
ASSERT_EQ(j1["happy"], true);
}
{
auto j2 = fs.readJson("json/file2.json");
//std::cout << "dump: \n" << std::setw(4) << j2 << std::endl;
ASSERT_FALSE(j2.empty());
ASSERT_EQ(j2["answer"], 42);
}
engine.disableService<MM::Services::FilesystemService>();
}
TEST(fs_service_system, write_json) {
MM::Engine engine;
engine.addService<MM::Services::FilesystemService>(argv0, "filesystem_Test");
ASSERT_TRUE(engine.enableService<MM::Services::FilesystemService>());
auto* fs_ss_ptr = engine.tryService<MM::Services::FilesystemService>();
ASSERT_NE(fs_ss_ptr, nullptr);
auto& fs = *fs_ss_ptr;
//ASSERT_TRUE(PHYSFS_mountMemory(test_zip, test_zip_len, NULL, "", NULL, 0));
// taken from json README.md
nlohmann::json j = {
{"pi", 3.141},
{"happy", true},
{"name", "Niels"},
{"nothing", nullptr},
{"answer", {
{"everything", 42}
}},
{"list", {1, 0, 2}},
{"object", {
{"currency", "USD"},
{"value", 42.99}
}}
};
ASSERT_TRUE(fs.writeJson("write_test.json", j, 4));
engine.disableService<MM::Services::FilesystemService>();
}
//FS_CONST_MOUNT_FILE_STATIC("static_test_file.txt",
//R"(test text.)")
int main(int argc, char** argv) {
argv0 = argv[0];
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}

Binary file not shown.

View File

@ -0,0 +1,64 @@
unsigned char test_zip[] = {
0x50, 0x4b, 0x03, 0x04, 0x14, 0x00, 0x08, 0x00, 0x08, 0x00, 0xfa, 0x64,
0x42, 0x4e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00,
0x00, 0x00, 0x0d, 0x00, 0x20, 0x00, 0x74, 0x65, 0x73, 0x74, 0x5f, 0x66,
0x69, 0x6c, 0x65, 0x2e, 0x74, 0x78, 0x74, 0x55, 0x54, 0x0d, 0x00, 0x07,
0x89, 0x81, 0x55, 0x5c, 0x20, 0x0b, 0x87, 0x5c, 0x89, 0x81, 0x55, 0x5c,
0x75, 0x78, 0x0b, 0x00, 0x01, 0x04, 0xe8, 0x03, 0x00, 0x00, 0x04, 0xe8,
0x03, 0x00, 0x00, 0x2b, 0x49, 0x2d, 0x2e, 0x51, 0xb0, 0x72, 0xe1, 0x02,
0x00, 0x50, 0x4b, 0x07, 0x08, 0x46, 0xb6, 0x5c, 0xe8, 0x0a, 0x00, 0x00,
0x00, 0x08, 0x00, 0x00, 0x00, 0x50, 0x4b, 0x03, 0x04, 0x14, 0x00, 0x08,
0x00, 0x08, 0x00, 0xe1, 0xbc, 0x7e, 0x4e, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0xb8, 0x05, 0x00, 0x00, 0x1b, 0x00, 0x20, 0x00, 0x77,
0x61, 0x6c, 0x6c, 0x5f, 0x63, 0x6f, 0x6e, 0x63, 0x72, 0x65, 0x74, 0x65,
0x2d, 0x31, 0x5f, 0x73, 0x65, 0x5f, 0x30, 0x2e, 0x32, 0x2e, 0x6a, 0x73,
0x6f, 0x6e, 0x55, 0x54, 0x0d, 0x00, 0x07, 0x06, 0xf0, 0x9f, 0x5c, 0x06,
0xf0, 0x9f, 0x5c, 0x06, 0xf0, 0x9f, 0x5c, 0x75, 0x78, 0x0b, 0x00, 0x01,
0x04, 0xe8, 0x03, 0x00, 0x00, 0x04, 0xe8, 0x03, 0x00, 0x00, 0x95, 0x94,
0x61, 0x8b, 0x84, 0x20, 0x10, 0x86, 0xbf, 0xf7, 0x2b, 0xc2, 0xcf, 0xb5,
0xa4, 0xd6, 0xc2, 0xf5, 0x57, 0x8e, 0x25, 0xa4, 0xa4, 0x84, 0xb2, 0x48,
0xf7, 0x96, 0x63, 0xd9, 0xff, 0x7e, 0x93, 0x9d, 0x1e, 0x0b, 0x67, 0x69,
0x04, 0xc9, 0x8c, 0x3e, 0xbe, 0x33, 0x0d, 0xef, 0x33, 0x45, 0xed, 0x3c,
0xde, 0x27, 0xa9, 0x50, 0x8d, 0x71, 0x96, 0xa4, 0x48, 0x4c, 0xac, 0xe7,
0xa8, 0x46, 0x0f, 0x36, 0x8e, 0x4d, 0x3b, 0xcb, 0x76, 0xe5, 0x9a, 0xe7,
0xb8, 0x51, 0xbc, 0x29, 0x2e, 0xe4, 0xb2, 0xc8, 0x1e, 0xb9, 0x6d, 0x03,
0x17, 0xfd, 0xa0, 0xe1, 0xe4, 0xb5, 0x70, 0xb1, 0x87, 0xe8, 0xf4, 0x80,
0x6a, 0x5a, 0x91, 0x2d, 0x34, 0xb1, 0xb5, 0x17, 0x12, 0xd5, 0x26, 0x2f,
0xd9, 0xe4, 0x27, 0x1b, 0xaa, 0x5a, 0x58, 0x2b, 0xe0, 0x86, 0x7d, 0xbf,
0xe6, 0xeb, 0xca, 0xc4, 0x26, 0xed, 0x33, 0x49, 0x7f, 0x9f, 0xa7, 0x5b,
0x39, 0x9e, 0x9a, 0x47, 0xd1, 0x6d, 0xc7, 0x5d, 0x42, 0x8b, 0x11, 0x12,
0xb4, 0x74, 0xa1, 0xd7, 0xcd, 0xf0, 0x20, 0xdc, 0xce, 0x77, 0x09, 0x8a,
0xab, 0xca, 0x06, 0xba, 0x2f, 0xbe, 0x2a, 0x31, 0x83, 0x46, 0x84, 0xa1,
0x3e, 0x8a, 0x6c, 0xc2, 0x16, 0x47, 0x89, 0x8d, 0x78, 0x85, 0xc0, 0xf5,
0x35, 0x7e, 0x13, 0xb0, 0x2b, 0x87, 0xfd, 0x45, 0x96, 0xee, 0x6f, 0x8e,
0x6f, 0x7f, 0x72, 0xb2, 0xd4, 0xcb, 0x21, 0x47, 0x9c, 0x1c, 0xc3, 0x27,
0x8c, 0x83, 0xfd, 0x20, 0x43, 0x09, 0x07, 0xd1, 0xff, 0x41, 0x96, 0x12,
0x0e, 0xfa, 0x38, 0x03, 0x05, 0xf7, 0xa8, 0x38, 0xac, 0x2d, 0xa2, 0x4b,
0xc4, 0xd3, 0x25, 0x8b, 0x89, 0x41, 0x1d, 0xf4, 0x29, 0xae, 0xe3, 0xa4,
0x3c, 0x25, 0x85, 0x76, 0x8a, 0x1e, 0xff, 0xbc, 0x88, 0xf2, 0xa8, 0x47,
0x54, 0x11, 0x39, 0x05, 0xb4, 0x3a, 0x9b, 0xcb, 0xd0, 0xda, 0xca, 0x83,
0x36, 0xb9, 0x49, 0x08, 0x44, 0xf9, 0x45, 0xc5, 0x92, 0xae, 0xa7, 0xa3,
0xf9, 0x86, 0x72, 0xc6, 0x64, 0x7d, 0x73, 0x77, 0x9b, 0xef, 0x65, 0x73,
0x36, 0x63, 0x3a, 0x5c, 0x1b, 0x4b, 0x72, 0x36, 0x05, 0x2e, 0x95, 0xbc,
0x7e, 0x00, 0x50, 0x4b, 0x07, 0x08, 0x20, 0xba, 0x28, 0x91, 0x30, 0x01,
0x00, 0x00, 0xb8, 0x05, 0x00, 0x00, 0x50, 0x4b, 0x01, 0x02, 0x14, 0x03,
0x14, 0x00, 0x08, 0x00, 0x08, 0x00, 0xfa, 0x64, 0x42, 0x4e, 0x46, 0xb6,
0x5c, 0xe8, 0x0a, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x0d, 0x00,
0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xb4, 0x81,
0x00, 0x00, 0x00, 0x00, 0x74, 0x65, 0x73, 0x74, 0x5f, 0x66, 0x69, 0x6c,
0x65, 0x2e, 0x74, 0x78, 0x74, 0x55, 0x54, 0x0d, 0x00, 0x07, 0x89, 0x81,
0x55, 0x5c, 0x20, 0x0b, 0x87, 0x5c, 0x89, 0x81, 0x55, 0x5c, 0x75, 0x78,
0x0b, 0x00, 0x01, 0x04, 0xe8, 0x03, 0x00, 0x00, 0x04, 0xe8, 0x03, 0x00,
0x00, 0x50, 0x4b, 0x01, 0x02, 0x14, 0x03, 0x14, 0x00, 0x08, 0x00, 0x08,
0x00, 0xe1, 0xbc, 0x7e, 0x4e, 0x20, 0xba, 0x28, 0x91, 0x30, 0x01, 0x00,
0x00, 0xb8, 0x05, 0x00, 0x00, 0x1b, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0xb4, 0x81, 0x65, 0x00, 0x00, 0x00, 0x77,
0x61, 0x6c, 0x6c, 0x5f, 0x63, 0x6f, 0x6e, 0x63, 0x72, 0x65, 0x74, 0x65,
0x2d, 0x31, 0x5f, 0x73, 0x65, 0x5f, 0x30, 0x2e, 0x32, 0x2e, 0x6a, 0x73,
0x6f, 0x6e, 0x55, 0x54, 0x0d, 0x00, 0x07, 0x06, 0xf0, 0x9f, 0x5c, 0x06,
0xf0, 0x9f, 0x5c, 0x06, 0xf0, 0x9f, 0x5c, 0x75, 0x78, 0x0b, 0x00, 0x01,
0x04, 0xe8, 0x03, 0x00, 0x00, 0x04, 0xe8, 0x03, 0x00, 0x00, 0x50, 0x4b,
0x05, 0x06, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x02, 0x00, 0xc4, 0x00,
0x00, 0x00, 0xfe, 0x01, 0x00, 0x00, 0x00, 0x00
};
unsigned int test_zip_len = 728;

View File

@ -0,0 +1 @@
test :D

View File

@ -0,0 +1,80 @@
{ "columns":11,
"image":"wall_concrete-1_se_0.2.png",
"imageheight":160,
"imagewidth":352,
"margin":0,
"name":"wall_concrete-1_se_0.2",
"spacing":0,
"terrains":[
{
"name":"solid",
"tile":34
}],
"tilecount":55,
"tiledversion":"1.2.3",
"tileheight":32,
"tiles":[
{
"id":1,
"terrain":[0, 0, 0, -1]
},
{
"id":2,
"terrain":[0, 0, -1, 0]
},
{
"id":12,
"terrain":[0, -1, 0, 0]
},
{
"id":13,
"terrain":[-1, 0, 0, 0]
},
{
"id":19,
"terrain":[-1, 0, 0, -1]
},
{
"id":20,
"terrain":[0, -1, -1, 0]
},
{
"id":22,
"terrain":[-1, -1, -1, 0]
},
{
"id":23,
"terrain":[-1, -1, 0, 0]
},
{
"id":24,
"terrain":[-1, -1, 0, -1]
},
{
"id":33,
"terrain":[-1, 0, -1, 0]
},
{
"id":34,
"terrain":[0, 0, 0, 0]
},
{
"id":35,
"terrain":[0, -1, 0, -1]
},
{
"id":44,
"terrain":[-1, 0, -1, -1]
},
{
"id":45,
"terrain":[0, 0, -1, -1]
},
{
"id":46,
"terrain":[0, -1, -1, -1]
}],
"tilewidth":32,
"type":"tileset",
"version":1.2
}

View File

@ -0,0 +1,134 @@
cmake_minimum_required(VERSION 3.1 FATAL_ERROR)
project(imgui_lib CXX)
################## imgui_service
add_library(imgui_service
./src/mm/services/imgui_s.hpp
./src/mm/services/imgui_s.cpp
)
target_include_directories(imgui_service PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/src")
target_link_libraries(imgui_service
engine
imgui_render_task
)
################## imgui_widgets
add_library(imgui_widgets
./src/mm/imgui/widgets/knob.hpp
./src/mm/imgui/widgets/plot_radar.hpp
./src/mm/imgui/widgets/camera.hpp
./src/mm/imgui/widgets/entity.hpp
./src/mm/imgui/widgets/filesystem.hpp
./src/mm/imgui/widgets/spritesheet.hpp
./src/mm/imgui/widgets/texture.hpp
./src/mm/imgui/widgets/texture_resource_manager.hpp
./src/mm/imgui/widgets/auto_wrap.hpp
./src/mm/imgui/widgets/components/name.hpp
./src/mm/imgui/widgets/components/transform2d.hpp
./src/mm/imgui/widgets/components/transform3d.hpp
./src/mm/imgui/widgets/components/velocity2d.hpp
./src/mm/imgui/widgets/components/view_dir2d.hpp
./src/mm/imgui/widgets/components/view_dir3d.hpp
./src/mm/imgui/widgets/components/color.hpp
./src/mm/imgui/widgets/components/texture.hpp
############
./src/mm/imgui/widgets/knob.cpp
./src/mm/imgui/widgets/plot_radar.cpp
./src/mm/imgui/widgets/camera.cpp
./src/mm/imgui/widgets/entity.cpp
./src/mm/imgui/widgets/filesystem.cpp
./src/mm/imgui/widgets/spritesheet.cpp
./src/mm/imgui/widgets/texture.cpp
./src/mm/imgui/widgets/texture_resource_manager.cpp
./src/mm/imgui/widgets/components/name.cpp
./src/mm/imgui/widgets/components/transform2d.cpp
./src/mm/imgui/widgets/components/transform3d.cpp
./src/mm/imgui/widgets/components/velocity2d.cpp
./src/mm/imgui/widgets/components/view_dir2d.cpp
./src/mm/imgui/widgets/components/view_dir3d.cpp
./src/mm/imgui/widgets/components/color.cpp
./src/mm/imgui/widgets/components/texture.cpp
)
target_include_directories(imgui_widgets PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/src")
target_link_libraries(imgui_widgets
opengl_renderer_s
imgui_render_task
engine
common_components
)
################## imgui_tools
add_library(imgui_tools
./src/mm/imgui/imgui_entt_entity_editor.hpp
./src/mm/imgui/fps_overlay.hpp
./src/mm/imgui/file_text_editor.hpp
./src/mm/imgui/file_shader_editor.hpp
./src/mm/imgui/fps_overlay.cpp
./src/mm/imgui/file_text_editor.cpp
./src/mm/imgui/file_shader_editor.cpp
./src/mm/services/scene_tools.hpp
./src/mm/services/scene_tools.cpp
)
target_include_directories(imgui_tools PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/src")
target_link_libraries(imgui_tools
imgui_render_task
imgui_widgets
imgui_color_text_edit
)
################## imgui_sound
add_library(imgui_sound
./src/mm/imgui/sound_info.hpp
./src/mm/imgui/sound_pref.hpp
./src/mm/imgui/widgets/soloud.hpp
./src/mm/imgui/sound_info.cpp
./src/mm/imgui/sound_pref.cpp
# soloud.hpp imps:
./src/mm/imgui/widgets/soloud_sfxr.cpp
./src/mm/imgui/widgets/soloud_filter.cpp
)
target_include_directories(imgui_sound PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/src")
target_link_libraries(imgui_sound
imgui_render_task
imgui_widgets
sound_service
)
################## imgui_tilemap
add_library(imgui_tilemap
./src/mm/imgui/widgets/components/tilemap_renderable.hpp
./src/mm/imgui/widgets/components/tilemap_renderable.cpp
)
target_include_directories(imgui_tilemap PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/src")
target_link_libraries(imgui_tilemap
imgui_render_task
imgui_widgets
tilemap
)
if (BUILD_TESTING)
add_subdirectory(test)
endif()

View File

@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2015-present Ionic (http://ionic.io/)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

Binary file not shown.

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,130 @@
#include "./file_shader_editor.hpp"
#include <sstream>
#ifdef MM_OPENGL_3_GLES
//#include <SDL_opengles2_gl2.h>
#include <GLES3/gl3.h>
#else
#include <glad/glad.h>
#endif
#include <IconsIonicons.h>
#include <cstdlib>
//#include <charconv>
namespace MM {
FileShaderEditor::FileShaderEditor(Engine& engine) : FileTextEditor(engine) {
setLanguageDefinition(TextEditor::LanguageDefinition::GLSL());
shaderType = GL_FRAGMENT_SHADER;
_windowID = ICON_II_CODE " " ICON_II_IMAGE " File Shader Text Editor##";
_windowID += std::to_string(rand()); // lets hope they dont collide
}
void FileShaderEditor::postSave(void) {
checkErrors();
}
void FileShaderEditor::checkErrors(void) {
_markers.clear();
_te.SetErrorMarkers(_markers);
auto source = _te.GetText();
const char* src = source.c_str();
uint32_t id = glCreateShader(shaderType);
glShaderSource(id, 1, &src, nullptr);
glCompileShader(id);
int32_t res;
glGetShaderiv(id, GL_COMPILE_STATUS, &res);
int length;
glGetShaderiv(id, GL_INFO_LOG_LENGTH, &length);
if (length) {
char* msg = (char*) alloca(length * sizeof(char));
glGetShaderInfoLog(id, length, &length, msg);
handleErrorString(msg);
}
glDeleteShader(id);
}
void FileShaderEditor::handleErrorString(const char* msg) {
// TODO: detect compiler info format
handleErrorStringFormat1(msg);
}
// accepts error messages in this format
// msgs separated by newline
// [number?]:[line number](char in line): [type eg. "error"]: [error text]
// TODO: has alot of bugs, rework
void FileShaderEditor::handleErrorStringFormat1(const char* msg) {
std::istringstream stream;
stream.str(msg);
for (std::string line; std::getline(stream, line);) {
std::string_view view(line);
view.remove_prefix(view.find(':')+1);
auto next_pos = view.find('(');
char* ptr = nullptr;
unsigned int line_number = ::strtoul(&view[0], &ptr, 10);
if (ptr != &view[next_pos]) {
//std::cout << "error" << std::endl;
continue;
}
// no support yet :(
//unsigned int line_number = std::from_chars(&view[0], &view[next_pos-1], 10);
view.remove_prefix(next_pos);
_markers.insert(std::make_pair(line_number, view));
}
_te.SetErrorMarkers(_markers);
}
void FileShaderEditor::renderImGui(void) {
FileTextEditor::renderImGui();
// append to window
if (ImGui::Begin(_windowID.c_str())) {
if (ImGui::BeginMenuBar()) {
if (ImGui::BeginMenu("Shader")) {
if (ImGui::BeginMenu("Error Checks")) {
if (ImGui::MenuItem("Toggle Live Error Checks", nullptr, checkErrorsOnChange)) {
checkErrorsOnChange = !checkErrorsOnChange;
}
ImGui::EndMenu();
}
if (ImGui::BeginMenu("Shader Type")) {
if (ImGui::MenuItem("Vertex Shader", nullptr, shaderType == GL_VERTEX_SHADER)) {
shaderType = GL_VERTEX_SHADER;
}
if (ImGui::MenuItem("Fragment Shader", nullptr, shaderType == GL_FRAGMENT_SHADER)) {
shaderType = GL_FRAGMENT_SHADER;
}
ImGui::EndMenu();
}
ImGui::EndMenu();
}
ImGui::EndMenuBar();
}
}
ImGui::End();
if(checkErrorsOnChange && _te.IsTextChanged()) {
checkErrors();
}
}
} // MM

View File

@ -0,0 +1,30 @@
#pragma once
#include "./file_text_editor.hpp"
namespace MM {
class FileShaderEditor : public FileTextEditor {
protected:
TextEditor::ErrorMarkers _markers;
protected:
void postSave(void) override;
void checkErrors(void);
void handleErrorString(const char* msg);
void handleErrorStringFormat1(const char* msg);
public:
FileShaderEditor(Engine& engine);
void renderImGui(void);
public:
bool checkErrorsOnChange = true;
uint32_t shaderType;
};
} // MM

View File

@ -0,0 +1,236 @@
#include "./file_text_editor.hpp"
#include <mm/services/filesystem.hpp>
#include <physfs.h>
#include <algorithm>
#include <mm/imgui/widgets/filesystem.hpp>
#include <IconsIonicons.h>
#include <mm/logger.hpp>
#define LOGFTE(x) LOG("FileTextEditor", x)
namespace MM {
FileTextEditor::FileTextEditor(Engine& engine) {
_windowID = ICON_II_CODE " File Text Editor##";
_windowID += std::to_string(rand()); // lets hope they dont collide
_fs_ptr = engine.tryService<MM::Services::FilesystemService>();
}
void FileTextEditor::setLanguageDefinition(const TextEditor::LanguageDefinition& lang) {
_te_lang = lang;
_te.SetLanguageDefinition(_te_lang);
}
void FileTextEditor::renderPopups(void) {
if (ImGui::BeginPopupModal("Open File", nullptr,
ImGuiWindowFlags_NoDecoration |
ImGuiWindowFlags_AlwaysAutoResize |
ImGuiWindowFlags_NoSavedSettings)) {
MM::ImGuiWidgets::FilePicker("open", *_fs_ptr, _tmp_file_path);
if (ImGui::Button("Load File")) {
bool succ = open(_tmp_file_path);
ImGui::CloseCurrentPopup();
if (!succ) {
// TODO: handle error
LOGFTE("error: opening file");
}
}
ImGui::SameLine();
if (ImGui::Button("Cancel")) {
ImGui::CloseCurrentPopup();
}
ImGui::EndPopup();
}
if (ImGui::BeginPopupModal("Save File As", nullptr,
ImGuiWindowFlags_NoDecoration |
ImGuiWindowFlags_AlwaysAutoResize |
ImGuiWindowFlags_NoSavedSettings)) {
MM::ImGuiWidgets::FilePicker("save", *_fs_ptr, _tmp_file_path, true);
if (ImGui::Button("Save")) {
auto old = _file_path;
_file_path = _tmp_file_path;
if (!save()) {
LOGFTE(std::string("error: saving file as '") + _file_path + "'");
_file_path = old; // retore in case of error
}
ImGui::CloseCurrentPopup();
}
ImGui::SameLine();
if (ImGui::Button("Cancel")) {
ImGui::CloseCurrentPopup();
}
ImGui::EndPopup();
}
}
void FileTextEditor::renderImGui(bool* is_open) {
if (ImGui::Begin(_windowID.c_str(), is_open, /*ImGuiWindowFlags_HorizontalScrollbar | */ImGuiWindowFlags_MenuBar)) {
ImGui::SetWindowSize(ImVec2(800, 600), ImGuiCond_FirstUseEver);
bool open_file_open = false;
bool open_file_save = false;
if (ImGui::BeginMenuBar()) {
if (ImGui::BeginMenu("File")) {
if (ImGui::MenuItem("Open")) {
open_file_open = true;
}
if (ImGui::MenuItem("New File")) {
_file_path.clear();
_tmp_file_path.clear();
_te.SetText("");
}
if (ImGui::MenuItem("Save")) {
save();
}
if (ImGui::MenuItem("Save as")) {
open_file_save = true;
}
ImGui::EndMenu();
}
if (ImGui::BeginMenu("Edit")) {
if (_te.IsReadOnly() && ImGui::MenuItem("Enable Editing")) {
_te.SetReadOnly(false);
}
if (!_te.IsReadOnly() && ImGui::MenuItem("Disable Editing")) {
_te.SetReadOnly(true);
}
ImGui::EndMenu();
}
ImGui::EndMenuBar();
}
// popups
{
if (open_file_open)
ImGui::OpenPopup("Open File");
if (open_file_save)
ImGui::OpenPopup("Save File As");
renderPopups();
}
auto cpos = _te.GetCursorPosition();
ImGui::Text("%6d/%-6d %6d lines | %s | %s | %s | %s",
cpos.mLine + 1,
cpos.mColumn + 1,
_te.GetTotalLines(),
_te.IsOverwrite() ? "Ovr" : "Ins",
_te.CanUndo() ? "*" : " ",
_te.GetLanguageDefinition().mName.c_str(), _file_path.c_str());
_te.Render("TextEditor");
}
ImGui::End();
}
bool FileTextEditor::open(Services::FilesystemService::fs_file_t file, bool write) {
if (!file)
return false;
//auto& fs = MM::locator::filesystem_service::ref();
_fs_ptr->seek(file, 0);
std::string text;
//char buffer [265];
//while (auto r = _fs_ptr->read(file, buffer, 265)) {
//text.append(buffer, r);
//}
_fs_ptr->readString(file, text);
_te.SetText(text);
_te.SetReadOnly(!write);
postOpen();
return true;
}
bool FileTextEditor::open(const std::string& path, bool write) {
if (path.empty())
return false;
//auto& fs = MM::locator::filesystem_service::ref();
auto h = _fs_ptr->open(path.c_str());
if (!h)
return false;
_file_path = path;
// lang detection
{
// TODO: make better
std::string tmp = path.substr(path.size()-4);
std::transform(tmp.begin(), tmp.end(), tmp.begin(), [](unsigned char c) { return std::tolower(c); });
if (tmp == "glsl") {
setLanguageDefinition(TextEditor::LanguageDefinition::GLSL());
}
}
return open(h, write) && _fs_ptr->close(h); // lul
}
bool FileTextEditor::save(void) {
if (_file_path.empty()/* || !_fs_ptr->exists(_file_path.c_str())*/)
return false;
// create dir in write, if not exists (bug in physfs?)
{
std::string tmp = _file_path.substr(0, _file_path.find_last_of('/'));
if (!tmp.empty()) {
if (!PHYSFS_mkdir(tmp.c_str())) {
LOGFTE("error making dir");
}
}
}
auto h = _fs_ptr->open(_file_path.c_str(), Services::FilesystemService::WRITE);
if (!h) {
LOGFTE("error opening file for saving");
return false;
}
auto text = _te.GetText();
auto written = _fs_ptr->write(h, text.c_str(), text.size());
_fs_ptr->close(h);
if (written != text.size()) {
LOGFTE("written size not equal to text size");
return false;
}
postSave();
return true;
}
} // MM

View File

@ -0,0 +1,42 @@
#pragma once
#include <TextEditor.h>
#include <mm/services/filesystem.hpp>
namespace MM {
class FileTextEditor {
protected:
TextEditor _te;
TextEditor::LanguageDefinition _te_lang;
std::string _file_path;
std::string _tmp_file_path;
std::string _windowID;
Services::FilesystemService* _fs_ptr = nullptr;
protected:
bool open(Services::FilesystemService::fs_file_t file, bool write = true);
void renderPopups(void);
virtual void postSave(void) {}
virtual void postOpen(void) {}
public:
FileTextEditor(Engine& engine);
virtual ~FileTextEditor(void) {}
void renderImGui(bool* is_open = nullptr);
void setLanguageDefinition(const TextEditor::LanguageDefinition& lang);
bool open(const std::string& path, bool write = true);
bool save(void);
};
} // MM

View File

@ -0,0 +1,49 @@
#include "./fps_overlay.hpp"
#include <imgui/imgui.h>
#include <imgui_plot_var.hpp>
namespace MM {
void ImGuiSimpleFPSOverlay::renderImGui(void) {
const float DISTANCE = 10.0f;
ImVec2 window_pos = ImVec2(DISTANCE, DISTANCE + 15.f);
ImVec2 window_pos_pivot = ImVec2(0.f, 0.f);
ImGui::SetNextWindowPos(window_pos, ImGuiCond_Always, window_pos_pivot);
ImGui::SetNextWindowBgAlpha(0.2f);
ImGui::Begin("SimpleFPSOverlay", NULL,
ImGuiWindowFlags_NoMove |
ImGuiWindowFlags_NoTitleBar |
ImGuiWindowFlags_NoResize |
ImGuiWindowFlags_AlwaysAutoResize |
ImGuiWindowFlags_NoSavedSettings |
ImGuiWindowFlags_NoFocusOnAppearing |
ImGuiWindowFlags_NoNav
);
ImGui::Text("%.1f FPS", ImGui::GetIO().Framerate);
if (_show_plot) {
ImGui::Separator();
ImGui::PlotVar("frame time", ImGui::GetIO().DeltaTime, 0.f, 0.05f, 120);
}
if (ImGui::BeginPopupContextWindow()) {
if (ImGui::MenuItem("Show Plot", NULL, _show_plot)) {
_show_plot = !_show_plot;
}
ImGui::EndPopup();
}
ImGui::End();
ImGui::PlotVarFlushOldEntries();
}
} // namespace MM

View File

@ -0,0 +1,12 @@
#pragma once
namespace MM {
class ImGuiSimpleFPSOverlay {
private:
bool _show_plot = false;
public:
void renderImGui(void);
};
} // namespace MM

View File

@ -0,0 +1,309 @@
// for the license, see the end of the file
#pragma once
#include <map>
#include <set>
#include <functional>
#include <string>
#include <entt/entt.hpp>
#include <imgui.h>
#ifndef MM_IEEE_ASSERT
#define MM_IEEE_ASSERT(x) assert(x)
#endif
#define MM_IEEE_IMGUI_PAYLOAD_TYPE_ENTITY "MM_IEEE_ENTITY"
#ifndef MM_IEEE_ENTITY_WIDGET
#define MM_IEEE_ENTITY_WIDGET ::MM::EntityWidget
#endif
namespace MM {
template <class EntityType>
inline void EntityWidget(EntityType& e, entt::basic_registry<EntityType>& reg, bool dropTarget = false)
{
ImGui::PushID(static_cast<int>(entt::to_integral(e)));
if (reg.valid(e)) {
ImGui::Text("ID: %d", entt::to_integral(e));
} else {
ImGui::Text("Invalid Entity");
}
if (reg.valid(e)) {
if (ImGui::BeginDragDropSource(ImGuiDragDropFlags_SourceAllowNullID)) {
ImGui::SetDragDropPayload(MM_IEEE_IMGUI_PAYLOAD_TYPE_ENTITY, &e, sizeof(e));
ImGui::Text("ID: %d", entt::to_integral(e));
ImGui::EndDragDropSource();
}
}
if (dropTarget && ImGui::BeginDragDropTarget()) {
if (const ImGuiPayload* payload = ImGui::AcceptDragDropPayload(MM_IEEE_IMGUI_PAYLOAD_TYPE_ENTITY)) {
e = *(EntityType*)payload->Data;
}
ImGui::EndDragDropTarget();
}
ImGui::PopID();
}
template <class Component, class EntityType>
void ComponentEditorWidget([[maybe_unused]] entt::basic_registry<EntityType>& registry, [[maybe_unused]] EntityType entity) {}
template <class Component, class EntityType>
void ComponentAddAction(entt::basic_registry<EntityType>& registry, EntityType entity)
{
registry.template emplace<Component>(entity);
}
template <class Component, class EntityType>
void ComponentRemoveAction(entt::basic_registry<EntityType>& registry, EntityType entity)
{
registry.template remove<Component>(entity);
}
template <class EntityType>
class EntityEditor {
public:
using Registry = entt::basic_registry<EntityType>;
using ComponentTypeID = ENTT_ID_TYPE;
struct ComponentInfo {
using Callback = std::function<void(Registry&, EntityType)>;
std::string name;
Callback widget, create, destroy;
};
bool show_window = true;
private:
std::map<ComponentTypeID, ComponentInfo> component_infos;
bool entityHasComponent(Registry& registry, EntityType& entity, ComponentTypeID type_id)
{
ComponentTypeID type[] = { type_id };
return registry.runtime_view(std::cbegin(type), std::cend(type)).contains(entity);
}
public:
template <class Component>
ComponentInfo& registerComponent(const ComponentInfo& component_info)
{
auto index = entt::type_info<Component>::id();
[[maybe_unused]] auto [it, insert_result] = component_infos.insert_or_assign(index, component_info);
MM_IEEE_ASSERT(insert_result);
return std::get<ComponentInfo>(*it);
}
template <class Component>
ComponentInfo& registerComponent(const std::string& name, typename ComponentInfo::Callback widget)
{
return registerComponent<Component>(ComponentInfo{
name,
widget,
ComponentAddAction<Component, EntityType>,
ComponentRemoveAction<Component, EntityType>,
});
}
template <class Component>
ComponentInfo& registerComponent(const std::string& name)
{
return registerComponent<Component>(name, ComponentEditorWidget<Component, EntityType>);
}
void renderEditor(Registry& registry, EntityType& e)
{
ImGui::TextUnformatted("Editing:");
ImGui::SameLine();
MM_IEEE_ENTITY_WIDGET(e, registry, true);
if (ImGui::Button("New")) {
e = registry.create();
}
if (registry.valid(e)) {
ImGui::SameLine();
// clone would go here
//if (ImGui::Button("Clone")) {
//auto old_e = e;
//e = registry.create();
//}
ImGui::Dummy({10, 0}); // space destroy a bit, to not accidentally click it
ImGui::SameLine();
// red button
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.65f, 0.15f, 0.15f, 1.f));
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.8f, 0.3f, 0.3f, 1.f));
ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(1.f, 0.2f, 0.2f, 1.f));
if (ImGui::Button("Destroy")) {
registry.destroy(e);
e = entt::null;
}
ImGui::PopStyleColor(3);
}
ImGui::Separator();
if (registry.valid(e)) {
ImGui::PushID(static_cast<int>(entt::to_integral(e)));
std::map<ComponentTypeID, ComponentInfo> has_not;
for (auto& [component_type_id, ci] : component_infos) {
if (entityHasComponent(registry, e, component_type_id)) {
ImGui::PushID(component_type_id);
if (ImGui::Button("-")) {
ci.destroy(registry, e);
ImGui::PopID();
continue; // early out to prevent access to deleted data
} else {
ImGui::SameLine();
}
if (ImGui::CollapsingHeader(ci.name.c_str())) {
ImGui::Indent(30.f);
ImGui::PushID("Widget");
ci.widget(registry, e);
ImGui::PopID();
ImGui::Unindent(30.f);
}
ImGui::PopID();
} else {
has_not[component_type_id] = ci;
}
}
if (!has_not.empty()) {
if (ImGui::Button("+ Add Component")) {
ImGui::OpenPopup("Add Component");
}
if (ImGui::BeginPopup("Add Component")) {
ImGui::TextUnformatted("Available:");
ImGui::Separator();
for (auto& [component_type_id, ci] : has_not) {
ImGui::PushID(component_type_id);
if (ImGui::Selectable(ci.name.c_str())) {
ci.create(registry, e);
}
ImGui::PopID();
}
ImGui::EndPopup();
}
}
ImGui::PopID();
}
}
void renderEntityList(Registry& registry, std::set<ComponentTypeID>& comp_list)
{
ImGui::Text("Components Filter:");
ImGui::SameLine();
if (ImGui::SmallButton("clear")) {
comp_list.clear();
}
ImGui::Indent();
for (const auto& [component_type_id, ci] : component_infos) {
bool is_in_list = comp_list.count(component_type_id);
bool active = is_in_list;
ImGui::Checkbox(ci.name.c_str(), &active);
if (is_in_list && !active) { // remove
comp_list.erase(component_type_id);
} else if (!is_in_list && active) { // add
comp_list.emplace(component_type_id);
}
}
ImGui::Unindent();
ImGui::Separator();
if (comp_list.empty()) {
ImGui::Text("Orphans:");
registry.orphans([&registry](auto e){
MM_IEEE_ENTITY_WIDGET(e, registry, false);
});
} else {
auto view = registry.runtime_view(comp_list.begin(), comp_list.end());
ImGui::Text("%lu Entities Matching:", view.size());
if (ImGui::BeginChild("entity list")) {
for (auto e : view) {
MM_IEEE_ENTITY_WIDGET(e, registry, false);
}
}
ImGui::EndChild();
}
}
[[deprecated("Use renderEditor() instead. And manage the window yourself.")]]
void render(Registry& registry, EntityType& e)
{
if (show_window) {
if (ImGui::Begin("Entity Editor", &show_window)) {
renderEditor(registry, e);
}
ImGui::End();
}
}
// displays both, editor and list
// uses static internally, use only as a quick way to get going!
void renderSimpleCombo(Registry& registry, EntityType& e)
{
if (show_window) {
ImGui::SetNextWindowSize(ImVec2(550, 400), ImGuiCond_FirstUseEver);
if (ImGui::Begin("Entity Editor", &show_window)) {
if (ImGui::BeginChild("list", {200, 0}, true)) {
static std::set<ComponentTypeID> comp_list;
renderEntityList(registry, comp_list);
}
ImGui::EndChild();
ImGui::SameLine();
if (ImGui::BeginChild("editor")) {
renderEditor(registry, e);
}
ImGui::EndChild();
}
ImGui::End();
}
}
};
} // MM
// MIT License
// Copyright (c) 2020 Erik Scholz, Gnik Droy
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.

View File

@ -0,0 +1,33 @@
#include "./sound_info.hpp"
#include <imgui/imgui.h>
#include <mm/services/sound_service.hpp>
#include <mm/engine.hpp>
namespace MM {
void ImGuiSoundInfo(Engine& engine) {
if (ImGui::Begin("Sound Info")) {
auto& sound = engine.getService<MM::Services::SoundService>();
ImGui::Text("SoLoud v%d", sound.engine.getVersion());
ImGui::Text("Backend: %s, ch: %d, rate: %d, buffersize: %d",
sound.engine.getBackendString(),
sound.engine.getBackendChannels(),
sound.engine.getBackendSamplerate(),
sound.engine.getBackendBufferSize());
ImGui::Text("Max Active Voice Count: %d", sound.engine.getMaxActiveVoiceCount());
ImGui::Text("Active Voice Count: %d", sound.engine.getActiveVoiceCount());
sound.engine.getWave();
ImGui::PlotLines("##Wave", sound.engine.getWave(), 256, 0, "Wave", -1.f, 1.f, ImVec2(0, 80));
ImGui::PlotHistogram("##Spectrum", sound.engine.calcFFT(), 256 / 2, 0, "FFT", 0.f, 20.f, ImVec2(0, 80));
}
ImGui::End();
}
} // MM

View File

@ -0,0 +1,8 @@
#pragma once
namespace MM {
class Engine; // fwd
void ImGuiSoundInfo(Engine& engine);
} // MM

View File

@ -0,0 +1,21 @@
#include "sound_pref.hpp"
#include <imgui/imgui.h>
#include <mm/services/sound_service.hpp>
namespace MM {
void ImGuiSoundPref(Engine& engine) {
if (ImGui::Begin("Sound Preferences")) {
auto& e = engine.getService<MM::Services::SoundService>().engine;
auto gvolume = e.getGlobalVolume();
ImGui::SliderFloat("Global Volume", &gvolume, 0.f, 1.f);
e.setGlobalVolume(gvolume);
}
ImGui::End();
}
} // MM

View File

@ -0,0 +1,8 @@
#pragma once
namespace MM {
class Engine; // fwd
void ImGuiSoundPref(Engine& engine);
} // MM

View File

@ -0,0 +1,34 @@
#pragma once
#include <functional>
#include <imgui/imgui.h>
namespace MM::ImGuiWidgets {
template<typename Iterator>
void ListWrap(Iterator begin, Iterator end, std::function<void(Iterator)> fn, float item_with) {
ImGuiStyle& style = ImGui::GetStyle();
float window_visible_x2 = ImGui::GetWindowPos().x + ImGui::GetWindowContentRegionMax().x;
for (auto it = begin; it != end; it++) {
//ImGui::PushID(n);
//ImGui::Button("Box", button_sz);
fn(it);
float last_item_x2 = ImGui::GetItemRectMax().x;
float next_item_x2 = last_item_x2 + style.ItemSpacing.x + item_with; // Expected position if next item was on same line
if (it + 1 != end && next_item_x2 < window_visible_x2) {
ImGui::SameLine();
}
//ImGui::PopID();
}
}
}

View File

@ -0,0 +1,77 @@
#include "./camera.hpp"
#include <imgui/imgui.h>
#include "./entity.hpp"
#include <mm/components/transform2d.hpp>
#include <entt/entity/registry.hpp>
namespace MM::ImGuiWidgets {
void Camera3D(MM::Scene& scene) {
ImGui::TextUnformatted("Camera:");
ImGui::Indent();
auto* camera = scene.try_ctx<MM::OpenGL::Camera3D>();
if (!camera) {
ImGui::TextUnformatted("NO CAMERA!");
return;
}
static bool follow_entity = false;
static MM::Entity tracking = entt::null;
ImGui::InputFloat("screenRatio", &camera->screenRatio);
ImGui::InputFloat("nearPlane", &camera->nearPlane);
ImGui::InputFloat("farPlane", &camera->farPlane);
if (camera->ortho) {
ImGui::TextUnformatted("orthographic mode");
ImGui::Checkbox("follow entity", &follow_entity);
if (follow_entity) {
ImGui::SameLine();
MM::ImGuiWidgets::Entity(tracking, scene);
if (scene.valid(tracking)) {
if (scene.has<MM::Components::Transform2D>(tracking)) {
camera->translation = {scene.get<MM::Components::Transform2D>(tracking).position, 0.f};
} else {
ImGui::TextUnformatted("error: Entity has no Transform");
}
}
} else {
ImGui::DragFloat2("translation", &camera->translation.x, 0.1f);
}
ImGui::DragFloat("h_vp_size", &camera->horizontalViewPortSize, 0.1f);
// TODO: aspect ratio
// TODO: check for change
camera->setOrthographic();
} else { // perspective
ImGui::TextUnformatted("perspective mode");
ImGui::DragFloat3("translation", &camera->translation.x, 0.1f);
ImGui::SliderFloat("fov", &camera->fov, 0.1f, glm::pi<float>());
ImGui::SliderFloat("yaw", &camera->yaw, 0.0f, 2*glm::pi<float>());
ImGui::SliderFloat("pitch", &camera->pitch, -glm::pi<float>()/2, glm::pi<float>()/2);
ImGui::InputFloat3("up", &camera->up.x);
camera->setPerspective();
}
camera->updateView();
ImGui::Unindent();
}
}

View File

@ -0,0 +1,12 @@
#pragma once
#include <mm/opengl/camera_3d.hpp>
#include <mm/services/scene_service_interface.hpp>
namespace MM::ImGuiWidgets {
void Camera3D(MM::Scene& scene);
}

View File

@ -0,0 +1,21 @@
#include "color.hpp"
#include <imgui/imgui.h>
namespace MM::ImGuiWidgets::Components {
void Color(MM::Components::Color& c) {
ImGui::ColorEdit4("color##Color", &c.color[0]);
}
}
namespace MM {
template <>
void ComponentEditorWidget<Components::Color>(MM::Scene& reg, MM::Entity e) {
ImGuiWidgets::Components::Color(reg.get<Components::Color>(e));
}
} // MM

View File

@ -0,0 +1,15 @@
#pragma once
#include <mm/components/color.hpp>
#include <mm/imgui/imgui_entt_entity_editor.hpp>
#include <mm/engine_fwd.hpp>
namespace MM::ImGuiWidgets::Components {
void Color(MM::Components::Color& c);
}
namespace MM {
template <>
void ComponentEditorWidget<Components::Color>(MM::Scene& reg, MM::Entity e);
}

View File

@ -0,0 +1,25 @@
#include "name.hpp"
#include <imgui/imgui.h>
#include <imgui/misc/cpp/imgui_stdlib.h>
namespace MM::ImGuiWidgets::Components {
// TODO: make editable
void Name(MM::Components::Name& n) {
if (ImGui::InputText("str##Name", &n.str)) {
n.str = n.str.substr(0, MM::Components::Name::max_str_len); // limit size
}
}
}
namespace MM {
template <>
void ComponentEditorWidget<Components::Name>(MM::Scene& reg, MM::Entity e) {
ImGuiWidgets::Components::Name(reg.get<Components::Name>(e));
}
} // MM

View File

@ -0,0 +1,15 @@
#pragma once
#include <mm/components/name.hpp>
#include <mm/imgui/imgui_entt_entity_editor.hpp>
#include <mm/services/scene_service_interface.hpp>
namespace MM::ImGuiWidgets::Components {
void Name(MM::Components::Name& n);
}
namespace MM {
template <>
void ComponentEditorWidget<Components::Name>(MM::Scene& reg, MM::Entity e);
}

View File

@ -0,0 +1,22 @@
#include "texture.hpp"
#include "../texture.hpp"
#include <imgui/imgui.h>
namespace MM::ImGuiWidgets::Components {
void Texture(MM::Components::OpenGL::Texture& tex) {
LabelTexture("tex##Texture", tex.tex);
}
}
namespace MM {
template <>
void ComponentEditorWidget<Components::OpenGL::Texture>(MM::Scene& reg, MM::Entity e) {
ImGuiWidgets::Components::Texture(reg.get<Components::OpenGL::Texture>(e));
}
} // MM

View File

@ -0,0 +1,17 @@
#pragma once
#include <mm/opengl/components/texture.hpp>
#include <mm/imgui/imgui_entt_entity_editor.hpp>
#include <mm/engine_fwd.hpp>
namespace MM::ImGuiWidgets::Components {
void Texture(MM::Components::OpenGL::Texture& tex);
}
namespace MM {
template <>
void ComponentEditorWidget<Components::OpenGL::Texture>(MM::Scene& reg, MM::Entity e);
}

View File

@ -0,0 +1,32 @@
#include "./tilemap_renderable.hpp"
#include <mm/opengl/render_tasks/tilemap_renderable.hpp>
#include <imgui/imgui.h>
#include <mm/imgui/widgets/spritesheet.hpp>
namespace MM::ImGuiWidgets::Components {
void TilemapRenderable(MM::OpenGL::TilemapRenderable& tm_r) {
ImGui::InputFloat("z", &tm_r.z);
for (size_t i = 0; i < tm_r.sprite_layer.size(); i++) {
ImGui::Separator();
std::string label = "sprite_sheet##";
label += std::to_string(i);
MM::ImGuiWidgets::LabelSpriteSheet(label.c_str(), tm_r.sprite_layer[i].sprite_sheet);
}
}
}
namespace MM {
template <>
void ComponentEditorWidget<OpenGL::TilemapRenderable>(MM::Scene& reg, MM::Entity e) {
ImGuiWidgets::Components::TilemapRenderable(reg.get<OpenGL::TilemapRenderable>(e));
}
} // MM

View File

@ -0,0 +1,21 @@
#pragma once
#include <mm/imgui/imgui_entt_entity_editor.hpp>
#include <mm/engine_fwd.hpp>
// fwd
namespace MM::OpenGL {
struct TilemapRenderable;
}
namespace MM::ImGuiWidgets::Components {
void TilemapRenderable(MM::OpenGL::TilemapRenderable& tm_r);
}
namespace MM {
template <>
void ComponentEditorWidget<OpenGL::TilemapRenderable>(MM::Scene& reg, MM::Entity e);
}

View File

@ -0,0 +1,26 @@
#include "./transform2d.hpp"
#include "mm/components/transform2d.hpp"
#include <imgui/imgui.h>
#include <glm/gtc/constants.hpp>
namespace MM::ImGuiWidgets::Components {
void Transform(MM::Components::Transform2D& t) {
ImGui::DragFloat2("position (x,y)##Transform", &t.position.x, 0.1f);
ImGui::SliderFloat("rotation##Transform", &t.rotation, 0.f, glm::pi<float>() * 2.f);
ImGui::DragFloat2("scale (x,y)##Transform", &t.scale.x, 1.f, 0.f);
}
}
namespace MM {
template <>
void ComponentEditorWidget<Components::Transform2D>(MM::Scene& reg, MM::Entity e) {
ImGuiWidgets::Components::Transform(reg.get<Components::Transform2D>(e));
}
} // MM

View File

@ -0,0 +1,15 @@
#pragma once
#include <mm/components/transform2d.hpp>
#include <mm/services/scene_service_interface.hpp>
#include <mm/imgui/imgui_entt_entity_editor.hpp>
namespace MM::ImGuiWidgets::Components {
void Transform(MM::Components::Transform2D& t);
}
namespace MM {
template <>
void ComponentEditorWidget<Components::Transform2D>(MM::Scene& reg, MM::Entity e);
}

View File

@ -0,0 +1,25 @@
#include "./transform3d.hpp"
#include <imgui/imgui.h>
#include <glm/gtc/constants.hpp>
namespace MM::ImGuiWidgets::Components {
void Transform(MM::Components::Transform3D& t) {
ImGui::DragFloat3("position (x,y,z)##Transform3D", &t.position.x, 0.1f);
ImGui::SliderFloat("rotation##Transform3D", &t.rotation, 0.f, glm::pi<float>() * 2.f);
ImGui::DragFloat3("scale (x,y,z)##Transform3D", &t.scale.x, 1.f, 0.f);
}
}
namespace MM {
template <>
void ComponentEditorWidget<Components::Transform3D>(MM::Scene& reg, MM::Entity e) {
ImGuiWidgets::Components::Transform(reg.get<Components::Transform3D>(e));
}
} // MM

View File

@ -0,0 +1,15 @@
#pragma once
#include <mm/components/transform3d.hpp>
#include <mm/imgui/imgui_entt_entity_editor.hpp>
#include <mm/services/scene_service_interface.hpp>
namespace MM::ImGuiWidgets::Components {
void Transform(MM::Components::Transform3D& t);
}
namespace MM {
template <>
void ComponentEditorWidget<Components::Transform3D>(MM::Scene& reg, MM::Entity e);
}

View File

@ -0,0 +1,24 @@
#include "./velocity2d.hpp"
#include <imgui/imgui.h>
#include <glm/gtc/constants.hpp>
namespace MM::ImGuiWidgets::Components {
void Velocity(MM::Components::Velocity2D& v) {
ImGui::DragFloat2("velocity (x,y)##Velocity", &v.velocity.x, 0.1f);
ImGui::SliderFloat("rotation##Velocity", &v.rotation, -glm::two_pi<float>(), glm::two_pi<float>());
}
}
namespace MM {
template <>
void ComponentEditorWidget<Components::Velocity2D>(MM::Scene& reg, MM::Entity e) {
ImGuiWidgets::Components::Velocity(reg.get<Components::Velocity2D>(e));
}
} // MM

View File

@ -0,0 +1,15 @@
#pragma once
#include <mm/components/velocity2d.hpp>
#include <mm/imgui/imgui_entt_entity_editor.hpp>
#include <mm/services/scene_service_interface.hpp>
namespace MM::ImGuiWidgets::Components {
void Velocity(MM::Components::Velocity2D& v);
}
namespace MM {
template <>
void ComponentEditorWidget<Components::Velocity2D>(MM::Scene& reg, MM::Entity e);
}

View File

@ -0,0 +1,23 @@
#include "./view_dir2d.hpp"
#include <imgui/imgui.h>
#include <glm/gtc/constants.hpp>
namespace MM::ImGuiWidgets::Components {
void ViewDir(MM::Components::ViewDir2D& vd) {
ImGui::SliderFloat("dir##ViewDir2D", &vd.dir, 0.0f, glm::two_pi<float>());
}
} // MM::ImGuiWidgets::Components
namespace MM {
template <>
void ComponentEditorWidget<Components::ViewDir2D>(MM::Scene& reg, MM::Entity e) {
ImGuiWidgets::Components::ViewDir(reg.get<Components::ViewDir2D>(e));
}
} // MM

View File

@ -0,0 +1,15 @@
#pragma once
#include <mm/components/view_dir2d.hpp>
#include <mm/imgui/imgui_entt_entity_editor.hpp>
#include <mm/services/scene_service_interface.hpp>
namespace MM::ImGuiWidgets::Components {
void ViewDir(MM::Components::ViewDir2D& t);
}
namespace MM {
template <>
void ComponentEditorWidget<Components::ViewDir2D>(MM::Scene& reg, MM::Entity e);
}

View File

@ -0,0 +1,25 @@
#include "./view_dir3d.hpp"
#include <imgui/imgui.h>
#include <glm/gtc/constants.hpp>
namespace MM::ImGuiWidgets::Components {
void ViewDir(MM::Components::ViewDir3D& vd) {
ImGui::SliderFloat("yaw##ViewDir3D", &vd.yaw, 0.0f, glm::two_pi<float>());
ImGui::SliderFloat("pitch##ViewDir3D", &vd.pitch, -glm::half_pi<float>(), glm::half_pi<float>());
ImGui::SliderFloat("roll##ViewDir3D", &vd.roll, -glm::half_pi<float>(), glm::half_pi<float>());
}
} // MM::ImGuiWidgets::Components
namespace MM {
template <>
void ComponentEditorWidget<Components::ViewDir3D>(MM::Scene& reg, MM::Entity e) {
ImGuiWidgets::Components::ViewDir(reg.get<Components::ViewDir3D>(e));
}
} // MM

View File

@ -0,0 +1,15 @@
#pragma once
#include <mm/components/view_dir3d.hpp>
#include <mm/imgui/imgui_entt_entity_editor.hpp>
#include <mm/services/scene_service_interface.hpp>
namespace MM::ImGuiWidgets::Components {
void ViewDir(MM::Components::ViewDir3D& t);
}
namespace MM {
template <>
void ComponentEditorWidget<Components::ViewDir3D>(MM::Scene& reg, MM::Entity e);
}

View File

@ -0,0 +1,94 @@
#include "entity.hpp"
#include <mm/imgui/imgui_entt_entity_editor.hpp>
#include <mm/components/name.hpp>
#include <IconsIonicons.h>
#include <entt/entity/registry.hpp>
namespace MM::ImGuiWidgets {
void Entity(MM::Entity& e, MM::Scene& ecs, bool dropTarget) {
ImGui::PushID(static_cast<int>(entt::to_integral(e)));
if (ecs.valid(e)) {
if (ecs.has<MM::Components::Name>(e)) {
ImGui::Text(ICON_II_CUBE "E: %d v%d (%s)", entt::to_integral(ecs.entity(e)), ecs.version(e), ecs.get<MM::Components::Name>(e).str.c_str());
} else {
ImGui::Text(ICON_II_CUBE "E: %d v%d", entt::to_integral(ecs.entity(e)), ecs.version(e));
}
} else {
ImGui::Text(ICON_II_CUBE "E: invalid");
}
if (ecs.valid(e)) {
if (ImGui::BeginDragDropSource(ImGuiDragDropFlags_SourceAllowNullID)) {
ImGui::SetDragDropPayload(MM_IEEE_IMGUI_PAYLOAD_TYPE_ENTITY, &e, sizeof(e));
ImGui::Text(ICON_II_CUBE "E: %d v%d", entt::to_integral(ecs.entity(e)), ecs.version(e));
ImGui::EndDragDropSource();
}
}
if (dropTarget && ImGui::BeginDragDropTarget()) {
if (const ImGuiPayload* payload = ImGui::AcceptDragDropPayload(MM_IEEE_IMGUI_PAYLOAD_TYPE_ENTITY)) {
e = *(MM::Entity*)payload->Data;
}
ImGui::EndDragDropTarget();
}
ImGui::PopID();
}
void EntityTrashCan(MM::Scene& ecs) {
ImGui::TextColored({1.f, 0.2f, 0.2f, 1.f}, ICON_II_TRASH_A " Entity TrashCan");
if (ImGui::BeginDragDropTarget()) {
if (const ImGuiPayload* payload = ImGui::AcceptDragDropPayload(MM_IEEE_IMGUI_PAYLOAD_TYPE_ENTITY)) {
auto e = *(MM::Entity*)payload->Data;
ecs.destroy(e);
}
ImGui::EndDragDropTarget();
}
}
//void EntityList(MM::Scene& ecs, const std::vector<component_type>& comps) {
//auto view = ecs.runtime_view(comps.begin(), comps.end());
//ImGui::Text("%lu Entities matching components:", view.size());
//if (ImGui::BeginChild("entity list")) {
//ImGui::Indent();
//for (auto e : view) {
//MM::ImGuiWidgets::Entity(e, ecs, false);
//}
//ImGui::Unindent();
//}
//ImGui::EndChild();
//}
//void EntityListByComponents(MM::Scene& ecs, std::map<component_type, std::pair<std::string, bool>>& named_comps) {
//ImGui::Text("Components:");
//ImGui::Indent();
//std::vector<component_type> comps;
//for (auto& it : named_comps) {
//ImGui::Checkbox(it.second.first.c_str(), &it.second.second);
//if (it.second.second) {
//comps.push_back(it.first);
//}
//}
//ImGui::Unindent();
//ImGui::Separator();
//EntityList(ecs, comps);
//}
}

View File

@ -0,0 +1,18 @@
#pragma once
#include <imgui/imgui.h>
#include <mm/services/scene_service_interface.hpp>
namespace MM::ImGuiWidgets {
void Entity(MM::Entity& e, MM::Scene& ecs, bool dropTarget = true);
// just a drop target
void EntityTrashCan(MM::Scene& ecs);
//void EntityList(MM::Scene& ecs, const std::vector<component_type>& comps);
//void EntityListByComponents(MM::Scene& ecs, std::map<component_type, std::pair<std::string, bool>>& named_comps);
}

View File

@ -0,0 +1,119 @@
#include "./filesystem.hpp"
#include <imgui/imgui.h>
#include <IconsIonicons.h>
#include <physfs.h>
namespace MM::ImGuiWidgets {
// also removes leafs
static std::string internal_remove_last_folder(const std::string& path) {
if (path.empty())
return path;
if (path == "/")
return path;
auto pos = path.find_last_of('/');
if (pos+1 == path.length()) {
pos = path.find_last_of('/', pos-1);
}
//path = path.substr(0, pos+1);
return path.substr(0, pos+1);
}
void FilePicker(const char* label, MM::Services::FilesystemService& fs, std::string& path, bool save) {
ImGui::PushID(label);
if (ImGui::Button("pick file")) {
ImGui::OpenPopup("file dialogue");
}
ImGui::SameLine();
ImGui::Text("\"%s\"", path.c_str());
if (ImGui::BeginPopup("file dialogue")) {
if (path.empty()) {
path = "/";
}
ImGui::Text("path: %s", path.c_str());
ImGui::Separator();
std::string dirlist_path = path;
if (dirlist_path.back() != '/')
dirlist_path = internal_remove_last_folder(dirlist_path);
//ImGui::TextUnformatted(dirlist_path.c_str());
if (dirlist_path != "/") {
if (ImGui::Selectable(ICON_II_FOLDER " ..", false, ImGuiSelectableFlags_DontClosePopups)) {
path = internal_remove_last_folder(dirlist_path);
}
}
// list folders and files
fs.forEachIn(dirlist_path.c_str(), [&](const char* i) -> bool {
std::string tmp_text;
std::string tmp_path = dirlist_path + i;
PHYSFS_Stat stat;
PHYSFS_stat(tmp_path.c_str(), &stat);
if (stat.filetype == PHYSFS_FILETYPE_DIRECTORY) {
tmp_text += ICON_II_FOLDER " ";
} else {
tmp_text += ICON_II_DOCUMENT " ";
}
tmp_text += i;
if (ImGui::Selectable(tmp_text.c_str(), false, ImGuiSelectableFlags_DontClosePopups)) {
if (path.back() != '/') {
path = internal_remove_last_folder(path);
}
path += i;
if (stat.filetype == PHYSFS_FILETYPE_DIRECTORY) {
path += '/';
} else if (!save) {
ImGui::CloseCurrentPopup();
}
}
return true;
});
if (save) {
ImGui::Separator();
// TODO: perf?
char buffer[201] = {};
std::string path_leaf = path.substr(path.find_last_of('/')+1);
strcpy(buffer, path_leaf.c_str());
if (ImGui::InputText("file name", buffer, 200)) {
// got input ?
if (path.back() != '/')
path = internal_remove_last_folder(path);
path += buffer;
}
if (ImGui::Button("confirm")) {
ImGui::CloseCurrentPopup();
}
}
ImGui::EndPopup();
}
ImGui::PopID();
}
}

View File

@ -0,0 +1,15 @@
#pragma once
#include <string>
#include <mm/services/filesystem.hpp>
namespace MM::ImGuiWidgets {
//#define IMGUI_PAYLOAD_TYPE_MM_FILE "MM_FILE"
//#define IMGUI_PAYLOAD_TYPE_MM_FILE_PATH "MM_FILE_PATH"
void FilePicker(const char* label, MM::Services::FilesystemService& fs, std::string& path, bool save = false);
}

View File

@ -0,0 +1,79 @@
#include "./knob.hpp"
#include <imgui/imgui.h>
#include <cmath>
namespace MM::ImGuiWidgets {
// this implementation is base on:
// https://github.com/Flix01/imgui/blob/imgui_with_addons/addons/imguivariouscontrols/imguivariouscontrols.cpp#L4119
// and has been modified.
//
// LICENSE of original code:
// This software is provided 'as-is', without any express or implied
// warranty. In no event will the authors be held liable for any damages
// arising from the use of this software.
// Permission is granted to anyone to use this software for any purpose,
// including commercial applications, and to alter it and redistribute it
// freely, subject to the following restrictions:
// 1. The origin of this software must not be misrepresented; you must not
// claim that you wrote the original software. If you use this software
// in a product, an acknowledgment in the product documentation would be
// appreciated but is not required.
// 2. Altered source versions must be plainly marked as such, and must not be
// misrepresented as being the original software.
// 3. This notice may not be removed or altered from any source distribution.
bool KnobFloat(const char* label, float* v, float v_min, float v_max, float step, bool show_tooltip) {
//@ocornut https://github.com/ocornut/imgui/issues/942
ImGuiIO& io = ImGui::GetIO();
ImGuiStyle& style = ImGui::GetStyle();
float radius_outer = 20.0f;
ImVec2 pos = ImGui::GetCursorScreenPos();
ImVec2 center = ImVec2(pos.x + radius_outer, pos.y + radius_outer);
float line_height = ImGui::GetTextLineHeight();
ImDrawList* draw_list = ImGui::GetWindowDrawList();
float ANGLE_MIN = 3.141592f * 0.75f;
float ANGLE_MAX = 3.141592f * 2.25f;
ImGui::InvisibleButton(label, ImVec2(radius_outer*2, radius_outer*2 + line_height + style.ItemInnerSpacing.y));
bool value_changed = false;
bool is_active = ImGui::IsItemActive();
bool is_hovered = ImGui::IsItemHovered();
if (is_active && io.MouseDelta.x != 0.0f) {
if (step<=0) step=50.f;
float tmp_step = (v_max - v_min) / step;
*v += io.MouseDelta.x * tmp_step;
if (*v < v_min) *v = v_min;
if (*v > v_max) *v = v_max;
value_changed = true;
} else if (is_hovered && (io.MouseDoubleClicked[0] || io.MouseClicked[2])) {
*v = (v_max + v_min) * 0.5f; // reset value
value_changed = true;
}
float t = (*v - v_min) / (v_max - v_min);
float angle = ANGLE_MIN + (ANGLE_MAX - ANGLE_MIN) * t;
float angle_cos = cosf(angle), angle_sin = sinf(angle);
float radius_inner = radius_outer*0.60f;
draw_list->AddCircleFilled(center, radius_outer, ImGui::GetColorU32(ImGuiCol_FrameBg), 16);
draw_list->AddLine(ImVec2(center.x + angle_cos*radius_inner, center.y + angle_sin*radius_inner), ImVec2(center.x + angle_cos*(radius_outer-2), center.y + angle_sin*(radius_outer-2)), ImGui::GetColorU32(ImGuiCol_SliderGrabActive), 2.0f);
draw_list->AddCircleFilled(center, radius_inner, ImGui::GetColorU32(is_active ? ImGuiCol_FrameBgActive : is_hovered ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg), 16);
draw_list->AddText(ImVec2(pos.x, pos.y + radius_outer * 2 + style.ItemInnerSpacing.y), ImGui::GetColorU32(ImGuiCol_Text), label);
if (show_tooltip && (is_active || is_hovered)) {
ImGui::SetNextWindowPos(ImVec2(pos.x - style.WindowPadding.x, pos.y - line_height - style.ItemInnerSpacing.y - style.WindowPadding.y));
ImGui::BeginTooltip();
ImGui::Text("%.3f", *v);
ImGui::EndTooltip();
}
return value_changed;
}
} // MM::ImGuiWidgets

View File

@ -0,0 +1,8 @@
#pragma once
namespace MM::ImGuiWidgets {
bool KnobFloat(const char* label, float* v, float v_min, float v_max, float step = 0.0f, bool show_tooltip = true);
}

View File

@ -0,0 +1,192 @@
#include "./plot_radar.hpp"
#include <glm/common.hpp> // min max
#include <glm/trigonometric.hpp> // sin cos
#include <glm/gtc/constants.hpp> // two_pi
namespace MM::ImGuiWidgets {
void PlotRadar(
const char* label,
const float* values, int values_count,
const char** vlabels,
float scale_min, float scale_max,
ImVec2 graph_size,
bool draw_net_first,
// polygon
const ImVec4& poly_line_color,
const ImVec4& poly_fill_color,
const float poly_line_thickness,
// net
const ImVec4& net_line_color,
const float net_line_thicknes
) {
if (values == nullptr || values_count == 0) {
return;
}
// set up scale
if (scale_min == FLT_MAX) {
scale_min = values[0];
for (int i = 1; i < values_count; i++) {
scale_min = glm::min(scale_min, values[i]);
}
}
if (scale_max == FLT_MAX) {
scale_max = values[0];
for (int i = 1; i < values_count; i++) {
scale_max = glm::max(scale_max, values[i]);
}
}
if (scale_min == scale_max) {
return;
}
ImGui::PushID(label);
ImDrawList* draw_list = ImGui::GetWindowDrawList();
const ImVec2 p = ImGui::GetCursorScreenPos();
const ImVec2 center_p {
p.x + graph_size.x/2,
p.y + graph_size.y/2,
};
const float inner_spacing_from_center = (graph_size.x/2) * 0.15f; // TODO: expose?
const float outer_spacing_from_center = (graph_size.x/2) * 0.8f; // TODO: expose?
auto draw_ngon = [&](const ImVec2& center, float radius, ImU32 col, int num_segments, float thickness) {
if ((col & IM_COL32_A_MASK) == 0 || num_segments <= 2)
return;
for (int i = 0; i < num_segments; i++) {
const float a0 = (float(i) / (num_segments-1)) * (glm::two_pi<float>()) * (num_segments-1 / float(num_segments)) - glm::half_pi<float>();
const float a1 = (float(i+1) / (num_segments-1)) * (glm::two_pi<float>()) * (num_segments-1 / float(num_segments)) - glm::half_pi<float>();
draw_list->AddLine(
{center.x + glm::cos(a0) * radius, center.y + glm::sin(a0) * radius},
{center.x + glm::cos(a1) * radius, center.y + glm::sin(a1) * radius},
col, thickness
);
}
};
auto draw_net = [&]() {
// inner ring
draw_ngon(center_p, inner_spacing_from_center, ImColor(net_line_color), values_count, net_line_thicknes);
// depends on min and max
draw_ngon(center_p, glm::mix(inner_spacing_from_center, outer_spacing_from_center, 0.5), ImColor(net_line_color), values_count, net_line_thicknes);
// outer ring
draw_ngon(center_p, outer_spacing_from_center, ImColor(net_line_color), values_count, net_line_thicknes);
// axies
for (int i = 0; i < values_count; i++) {
const float a = (float(i) / (values_count-1)) * (glm::two_pi<float>()) * (values_count-1 / float(values_count)) - glm::half_pi<float>();
draw_list->AddLine(
{center_p.x + glm::cos(a) * inner_spacing_from_center, center_p.y + glm::sin(a) * inner_spacing_from_center},
{center_p.x + glm::cos(a) * outer_spacing_from_center, center_p.y + glm::sin(a) * outer_spacing_from_center},
ImColor(net_line_color), net_line_thicknes
);
}
// lables
// TODO
};
auto value_to_radius = [&](float v) -> float {
//v -= scale_min;
//v /= scale_max;
//return v;
return (v - scale_min) / scale_max;
};
auto draw_polygon_fills = [&]() {
// fills
for (int i = 0; i < values_count; i++) {
const float a0 = (float(i) / (values_count-1)) * (glm::two_pi<float>()) * (values_count-1 / float(values_count)) - glm::half_pi<float>();
const float a1 = (float(i+1) / (values_count-1)) * (glm::two_pi<float>()) * (values_count-1 / float(values_count)) - glm::half_pi<float>();
float v0 = values[i];
float v1 = values[(i+1) % values_count];
v0 = value_to_radius(v0);
v1 = value_to_radius(v1);
float r0 = glm::mix(inner_spacing_from_center, outer_spacing_from_center, v0);
float r1 = glm::mix(inner_spacing_from_center, outer_spacing_from_center, v1);
draw_list->AddQuadFilled(
{center_p.x + glm::cos(a0) * r0, center_p.y + glm::sin(a0) * r0},
{center_p.x + glm::cos(a1) * r1, center_p.y + glm::sin(a1) * r1},
{center_p.x + glm::cos(a1) * inner_spacing_from_center, center_p.y + glm::sin(a1) * inner_spacing_from_center},
{center_p.x + glm::cos(a0) * inner_spacing_from_center, center_p.y + glm::sin(a0) * inner_spacing_from_center},
ImColor(poly_fill_color)
);
}
};
auto draw_polygon_lines = [&]() {
// lines
for (int i = 0; i < values_count; i++) {
const float a0 = (float(i) / (values_count-1)) * (glm::two_pi<float>()) * (values_count-1 / float(values_count)) - glm::half_pi<float>();
const float a1 = (float(i+1) / (values_count-1)) * (glm::two_pi<float>()) * (values_count-1 / float(values_count)) - glm::half_pi<float>();
float v0 = values[i];
float v1 = values[(i+1) % values_count];
v0 = value_to_radius(v0);
v1 = value_to_radius(v1);
float r0 = glm::mix(inner_spacing_from_center, outer_spacing_from_center, v0);
float r1 = glm::mix(inner_spacing_from_center, outer_spacing_from_center, v1);
draw_list->AddLine(
{center_p.x + glm::cos(a0) * r0, center_p.y + glm::sin(a0) * r0},
{center_p.x + glm::cos(a1) * r1, center_p.y + glm::sin(a1) * r1},
ImColor(poly_line_color), poly_line_thickness
);
}
};
auto draw_text = [&]() {
if (vlabels == nullptr) {
return;
}
for (int i = 0; i < values_count; i++) {
const float a0 = (float(i) / (values_count-1)) * (glm::two_pi<float>()) * (values_count-1 / float(values_count)) - glm::half_pi<float>();
const float text_spacing_from_center = (graph_size.x/2) * 0.9f;
draw_list->AddText(
{
center_p.x + glm::cos(a0) * text_spacing_from_center,
center_p.y + glm::sin(a0) * text_spacing_from_center - ImGui::GetTextLineHeight()/2
},
ImColor(1.f,1.f,1.f,1.f), vlabels[i]
);
}
};
if (draw_net_first) {
draw_net();
draw_polygon_fills();
draw_polygon_lines();
draw_text();
} else {
draw_polygon_fills();
draw_net();
draw_polygon_lines();
draw_text();
}
ImGui::PopID();
}
} // MM::ImGuiWidgets

View File

@ -0,0 +1,31 @@
#pragma once
//#include <cfloat>
#include <imgui/imgui.h>
namespace MM::ImGuiWidgets {
void PlotRadar(
const char* label,
const float* values, int values_count, // values offset?
const char** vlabels = nullptr, // list of null-terminated c-strings, same size as values
float scale_min = FLT_MAX, float scale_max = FLT_MAX,
ImVec2 graph_size = {100, 100},
bool draw_net_first = false,
// polygon
const ImVec4& poly_line_color = {0.8f, 0.8f, 0.0f, 1.0f},
const ImVec4& poly_fill_color = {0.8f, 0.8f, 0.0f, 0.4f},
const float poly_line_thickness = 2.f,
// net
const ImVec4& net_line_color = {0.9f, 0.9f, 0.9f, 0.8f},
const float net_line_thickness = 1.f
//bool show_tooltip = true
);
} // MM::ImGuiWidgets

View File

@ -0,0 +1,26 @@
#pragma once
// fwd
namespace SoLoud {
class Soloud;
class Sfxr;
struct SfxrParams;
class Filter;
class FilterInstance;
typedef unsigned int handle;
}
namespace MM::ImGuiWidgets {
bool SoLoudSfxrPlain(const char* label, SoLoud::SfxrParams* sfxr_params);
// uses knobs and childs
bool SoLoudSfxrFancy(const char* label, SoLoud::SfxrParams* sfxr_params);
// calls getParam*() methods
//bool SoLoudFilterLiveParams(const char* label, SoLoud::Filter* filter, SoLoud::FilterInstance* filter_instance); // no access to instance
bool SoLoudFilterLiveParams(const char* label, SoLoud::Soloud* soloud, SoLoud::Filter* filter, unsigned int filter_id, SoLoud::handle voice_handle = 0);
bool SoLoudFilterLiveParamsFancy(const char* label, SoLoud::Soloud* soloud, SoLoud::Filter* filter, unsigned int filter_id, SoLoud::handle voice_handle = 0, bool same_line = false);
} // MM::ImGuiWidgets

View File

@ -0,0 +1,134 @@
#include "./soloud.hpp"
#include <mm/imgui/widgets/knob.hpp>
#include <imgui/imgui.h>
#include <soloud.h>
#include <soloud_filter.h>
namespace MM::ImGuiWidgets {
//bool SoLoudFilterLiveParams(const char* label, SoLoud::Filter* filter, SoLoud::FilterInstance* filter_instance) {
//if (!filter || !filter_instance) return false;
//ImGui::PushID(label);
//bool value_changed = false;
//size_t param_count = filter->getParamCount();
//for (size_t i = 0; i < param_count; i++) {
//if (filter->getParamType(i) == SoLoud::Filter::PARAMTYPE::FLOAT_PARAM) {
//float tmp = filter_instance->getFilterParameter(i);
//if (ImGui::SliderFloat(
//filter->getParamName(i),
//&tmp,
//filter->getParamMin(i),
//filter->getParamMax(i)
//)) {
//filter_instance->setFilterParameter(i, tmp);
//value_changed = true;
//}
//} else if (filter->getParamType(i) == SoLoud::Filter::PARAMTYPE::INT_PARAM) {
//ImGui::Text("int %s min:%f max%f", filter->getParamName(i), filter->getParamMin(i), filter->getParamMax(i));
//} else if (filter->getParamType(i) == SoLoud::Filter::PARAMTYPE::BOOL_PARAM) {
//bool tmp = filter_instance->getFilterParameter(i) != filter->getParamMin(i);
//if (ImGui::Checkbox(filter->getParamName(i), &tmp)) {
//filter_instance->setFilterParameter(i, tmp ? filter->getParamMax(i) : filter->getParamMin(i));
//value_changed = true;
//}
//}
//}
//ImGui::PopID();
//return value_changed;
//}
bool SoLoudFilterLiveParams(const char* label, SoLoud::Soloud* soloud, SoLoud::Filter* filter, unsigned int filter_id, SoLoud::handle voice_handle) {
if (!filter) return false;
ImGui::PushID(label);
bool value_changed = false;
unsigned int param_count = static_cast<unsigned int>(filter->getParamCount());
for (unsigned int i = 0; i < param_count; i++) {
if (filter->getParamType(i) == SoLoud::Filter::PARAMTYPE::FLOAT_PARAM) {
float tmp = soloud->getFilterParameter(voice_handle, filter_id, i);
if (ImGui::SliderFloat(
filter->getParamName(i),
&tmp,
filter->getParamMin(i),
filter->getParamMax(i)
)) {
soloud->setFilterParameter(voice_handle, filter_id, i, tmp);
value_changed = true;
}
} else if (filter->getParamType(i) == SoLoud::Filter::PARAMTYPE::INT_PARAM) {
ImGui::Text("int %s min:%f max%f", filter->getParamName(i), filter->getParamMin(i), filter->getParamMax(i));
} else if (filter->getParamType(i) == SoLoud::Filter::PARAMTYPE::BOOL_PARAM) {
bool tmp = soloud->getFilterParameter(voice_handle, filter_id, i) != filter->getParamMin(i);
if (ImGui::Checkbox(filter->getParamName(i), &tmp)) {
soloud->setFilterParameter(voice_handle, filter_id, i, tmp ? filter->getParamMax(i) : filter->getParamMin(i));
value_changed = true;
}
}
}
ImGui::PopID();
return value_changed;
}
bool SoLoudFilterLiveParamsFancy(const char* label, SoLoud::Soloud* soloud, SoLoud::Filter* filter, unsigned int filter_id, SoLoud::handle voice_handle, bool same_line) {
if (!filter) return false;
ImGui::PushID(label);
bool value_changed = false;
unsigned int param_count = static_cast<unsigned int>(filter->getParamCount());
for (unsigned int i = 0; i < param_count; i++) {
if (filter->getParamType(i) == SoLoud::Filter::PARAMTYPE::FLOAT_PARAM) {
float tmp = soloud->getFilterParameter(voice_handle, filter_id, i);
if (MM::ImGuiWidgets::KnobFloat(
filter->getParamName(i),
&tmp,
filter->getParamMin(i),
filter->getParamMax(i),
400.f
)) {
soloud->setFilterParameter(voice_handle, filter_id, i, tmp);
value_changed = true;
}
} else if (filter->getParamType(i) == SoLoud::Filter::PARAMTYPE::INT_PARAM) {
ImGui::Text("int %s min:%f max%f", filter->getParamName(i), filter->getParamMin(i), filter->getParamMax(i));
} else if (filter->getParamType(i) == SoLoud::Filter::PARAMTYPE::BOOL_PARAM) {
bool tmp = soloud->getFilterParameter(voice_handle, filter_id, i) != filter->getParamMin(i);
if (ImGui::Checkbox(filter->getParamName(i), &tmp)) {
soloud->setFilterParameter(voice_handle, filter_id, i, tmp ? filter->getParamMax(i) : filter->getParamMin(i));
value_changed = true;
}
}
if (same_line && (i+1 != param_count)) {
ImGui::SameLine();
}
}
ImGui::PopID();
return value_changed;
}
} // MM::ImGuiWidgets

View File

@ -0,0 +1,235 @@
#include "./soloud.hpp"
#include <mm/imgui/widgets/knob.hpp>
#include <imgui/imgui.h>
#include <soloud_sfxr.h>
namespace MM::ImGuiWidgets {
bool SoLoudSfxrPlain(const char* label, SoLoud::SfxrParams* sfxr_params) {
ImGui::PushID(label);
bool value_changed = false;
{
const char* wlist[] {"SquareWave", "SawTooth", "SineWave", "Noise"};
if (ImGui::BeginCombo("Wave Type", wlist[sfxr_params->wave_type])) {
for (int i = 0; i < 4; i++) {
if (ImGui::Selectable(wlist[i])) {
sfxr_params->wave_type = i;
value_changed = true;
}
}
ImGui::EndCombo();
}
}
ImGui::Separator();
//internal_sec1();
{
value_changed |= ImGui::SliderFloat("Attack Time", &sfxr_params->p_env_attack, 0.f, 1.f);
value_changed |= ImGui::SliderFloat("Sustain Time", &sfxr_params->p_env_sustain, 0.f, 1.f);
value_changed |= ImGui::SliderFloat("Sustain Punch", &sfxr_params->p_env_punch, 0.f, 1.f);
value_changed |= ImGui::SliderFloat("Decay Time", &sfxr_params->p_env_decay, 0.f, 1.f);
}
ImGui::Separator();
//internal_sec2();
{
value_changed |= ImGui::SliderFloat("Start Freq", &sfxr_params->p_base_freq, 0.f, 1.f);
value_changed |= ImGui::SliderFloat("Min Freq", &sfxr_params->p_freq_limit, 0.f, 1.f);
value_changed |= ImGui::SliderFloat("Slide", &sfxr_params->p_freq_ramp, -1.f, 1.f);
value_changed |= ImGui::SliderFloat("Slide Delta", &sfxr_params->p_freq_dramp, -1.f, 1.f);
value_changed |= ImGui::SliderFloat("Vibrato Depth", &sfxr_params->p_vib_strength, 0.f, 1.f);
value_changed |= ImGui::SliderFloat("Vibrato Speed", &sfxr_params->p_vib_speed, 0.f, 1.f);
//ImGui::SliderFloat("Vibrato Delay", &sfxr_params->p_vib_delay, 0.f, 1.f); // useless?
}
ImGui::Separator();
//internal_sec3();
{
value_changed |= ImGui::SliderFloat("Change Amount", &sfxr_params->p_arp_mod, -1.f, 1.f);
value_changed |= ImGui::SliderFloat("Change Speed", &sfxr_params->p_arp_speed, 0.f, 1.f);
}
ImGui::Separator();
//internal_sec4();
{
value_changed |= ImGui::SliderFloat("Square Duty", &sfxr_params->p_duty, 0.f, 1.f);
value_changed |= ImGui::SliderFloat("Duty Sweep", &sfxr_params->p_duty_ramp, -1.f, 1.f);
}
ImGui::Separator();
//internal_sec5();
{
value_changed |= ImGui::SliderFloat("Repeat Speed", &sfxr_params->p_repeat_speed, 0.f, 1.f);
}
ImGui::Separator();
//internal_sec6();
{
value_changed |= ImGui::SliderFloat("Phaser Offset", &sfxr_params->p_pha_offset, -1.f, 1.f);
value_changed |= ImGui::SliderFloat("Phaser Sweep", &sfxr_params->p_pha_ramp, -1.f, 1.f);
}
ImGui::Separator();
//internal_sec7();
{
value_changed |= ImGui::SliderFloat("LP Filter Cutoff", &sfxr_params->p_lpf_freq, 0.f, 1.f);
value_changed |= ImGui::SliderFloat("LP Filter Cutoff Sweep", &sfxr_params->p_lpf_ramp, -1.f, 1.f);
value_changed |= ImGui::SliderFloat("LP Filter Resonace", &sfxr_params->p_lpf_resonance, 0.f, 1.f);
value_changed |= ImGui::SliderFloat("HP Filter Cutoff", &sfxr_params->p_hpf_freq, 0.f, 1.f);
value_changed |= ImGui::SliderFloat("HP Filter Cutoff Sweep", &sfxr_params->p_hpf_ramp, -1.f, 1.f);
}
ImGui::PopID();
return value_changed;
}
bool SoLoudSfxrFancy(const char* label, SoLoud::SfxrParams* sfxr_params) {
ImGui::PushID(label);
bool value_changed = false;
{
const char* wlist[] {"SquareWave", "SawTooth", "SineWave", "Noise"};
if (ImGui::BeginCombo("Wave Type", wlist[sfxr_params->wave_type])) {
for (int i = 0; i < 4; i++) {
if (ImGui::Selectable(wlist[i])) {
sfxr_params->wave_type = i;
value_changed = true;
}
}
ImGui::EndCombo();
}
}
// square wave mods
if (sfxr_params->wave_type == 0) {
if (ImGui::BeginChild("SquareWaveMod", ImVec2(105, 90), true)) {
ImGui::TextDisabled("SQW Mod");
value_changed |= KnobFloat("Duty", &sfxr_params->p_duty, 0.f, 1.f, 400.f);
ImGui::SameLine();
value_changed |= KnobFloat("Sweep", &sfxr_params->p_duty_ramp, -1.f, 1.f, 400.f);
}
ImGui::EndChild(); // SquareWaveMod
}
// +-------------+----+
// |EG |VOL |
// | O O O O | O |
// +----------+--+-+--+
// |FQ |ARP |
// | O O O | O |
// | O O O | O |
// +----------+----+
if (ImGui::BeginChild("EG", ImVec2(200, 90), true)) {
ImGui::TextDisabled("EG");
value_changed |= KnobFloat("Attack", &sfxr_params->p_env_attack, 0.f, 1.f, 400.f);
ImGui::SameLine();
value_changed |= KnobFloat("Sustain", &sfxr_params->p_env_sustain, 0.f, 1.f, 400.f);
ImGui::SameLine();
value_changed |= KnobFloat("Punch", &sfxr_params->p_env_punch, 0.f, 1.f, 400.f);
ImGui::SameLine();
value_changed |= KnobFloat("Decay", &sfxr_params->p_env_decay, 0.f, 1.f, 400.f);
}
ImGui::EndChild(); // EG
ImGui::SameLine();
if (ImGui::BeginChild("Vol", ImVec2(58, 90), true)) {
ImGui::TextDisabled("Vol");
value_changed |= KnobFloat("Volume", &sfxr_params->sound_vol, 0.f, 1.f, 400.f);
}
ImGui::EndChild(); // Vol
// next line
if (ImGui::BeginChild("Frequency", ImVec2(58, 152), true)) {
ImGui::TextDisabled("Freq");
value_changed |= KnobFloat("Base", &sfxr_params->p_base_freq, 0.f, 1.f, 400.f);
value_changed |= KnobFloat("Limit", &sfxr_params->p_freq_limit, 0.f, 1.f, 400.f);
}
ImGui::EndChild();
ImGui::SameLine();
if (ImGui::BeginChild("Slide", ImVec2(58, 152), true)) {
ImGui::TextDisabled("Slide");
value_changed |= KnobFloat("Slide", &sfxr_params->p_freq_ramp, -1.f, 1.f, 400.f);
value_changed |= KnobFloat("Delta", &sfxr_params->p_freq_dramp, -1.f, 1.f, 400.f);
}
ImGui::EndChild();
ImGui::SameLine();
if (ImGui::BeginChild("Vibrato", ImVec2(58, 152), true)) {
ImGui::TextDisabled("Vib");
value_changed |= KnobFloat("Depth", &sfxr_params->p_vib_strength, 0.f, 1.f, 400.f);
value_changed |= KnobFloat("Speed", &sfxr_params->p_vib_speed, 0.f, 1.f, 400.f);
}
ImGui::EndChild();
ImGui::SameLine();
if (ImGui::BeginChild("Arp", ImVec2(58, 152), true)) {
ImGui::TextDisabled("Arp");
value_changed |= KnobFloat("Amount", &sfxr_params->p_arp_mod, -1.f, 1.f, 400.f);
value_changed |= KnobFloat("Speed", &sfxr_params->p_arp_speed, 0.f, 1.f, 400.f);
}
ImGui::EndChild(); // Arp
// next line
if (ImGui::BeginChild("Repeat", ImVec2(58, 90), true)) {
ImGui::TextDisabled("Repeat");
value_changed |= KnobFloat("Speed", &sfxr_params->p_repeat_speed, 0.f, 1.f, 400.f);
}
ImGui::EndChild(); // Repeat
ImGui::SameLine();
if (ImGui::BeginChild("Phaser", ImVec2(105, 90), true)) {
ImGui::TextDisabled("Phaser");
value_changed |= KnobFloat("Offset", &sfxr_params->p_pha_offset, -1.f, 1.f, 400.f);
ImGui::SameLine();
value_changed |= KnobFloat("Sweep", &sfxr_params->p_pha_ramp, -1.f, 1.f, 400.f);
}
ImGui::EndChild(); // Phaser
// next line
if (ImGui::BeginChild("LP Filter", ImVec2(152, 90), true)) {
ImGui::TextDisabled("LP Filter");
value_changed |= KnobFloat("Cutoff", &sfxr_params->p_lpf_freq, 0.f, 1.f, 400.f);
ImGui::SameLine();
value_changed |= KnobFloat("Sweep", &sfxr_params->p_lpf_ramp, -1.f, 1.f, 400.f);
ImGui::SameLine();
value_changed |= KnobFloat("Peak", &sfxr_params->p_lpf_resonance, 0.f, 1.f, 400.f);
}
ImGui::EndChild(); // LP Filter
ImGui::SameLine();
if (ImGui::BeginChild("HP Filter", ImVec2(105, 90), true)) {
ImGui::TextDisabled("HP Filter");
value_changed |= KnobFloat("Cutoff", &sfxr_params->p_hpf_freq, 0.f, 1.f, 400.f);
ImGui::SameLine();
value_changed |= KnobFloat("Sweep", &sfxr_params->p_hpf_ramp, -1.f, 1.f, 400.f);
}
ImGui::EndChild(); // HP Filter
ImGui::PopID();
return value_changed;
}
} // MM::ImGuiWidgets

View File

@ -0,0 +1,117 @@
#include "./spritesheet.hpp"
#include <mm/opengl/spritesheet.hpp>
#include <imgui/imgui.h>
#include <imgui/imgui_internal.h>
#include "./texture.hpp"
#include <mm/logger.hpp>
namespace MM::ImGuiWidgets {
void SpriteSheetPreview(MM::OpenGL::SpriteSheet& sheet, ImVec2 size) {
ImDrawList* draw_list = ImGui::GetWindowDrawList();
const ImVec2 p = ImGui::GetCursorScreenPos();
const ImVec2 uv_a {0.f, 1.f};
const ImVec2 uv_b {1.f, 0.f};
draw_list->AddImage(sheet.tex->getHandle(), p, {p.x + size.x, p.y + size.y}, uv_a, uv_b);
const ImU32 line_col = ImColor(ImVec4(1.f, 0.f, 0.f, 1.f));
// TODO: tile size?
// v lines
for (uint32_t i = 0; i <= sheet.tile_count.x; i++) {
const float scale = size.x/sheet.tile_count.x;
const float x = p.x + scale*(float)i;
draw_list->AddLine({x, p.y}, {x, p.y + size.y}, line_col);
}
// h lines
for (uint32_t i = 0; i <= sheet.tile_count.y; i++) {
const float scale = size.y/sheet.tile_count.y;
const float y = p.y + scale*(float)i;;
draw_list->AddLine({p.x, y}, {p.x + size.x, y}, line_col);
}
}
void SpriteSheet(MM::OpenGL::SpriteSheet& sheet) {
const float w_all = ImGui::CalcItemWidth();
const ImVec2 dims {w_all, w_all * ((float)sheet.tex->height/sheet.tex->width)};
SpriteSheetPreview(sheet, dims);
// TODO: also activate by drag n drop targeting
if (ImGui::InvisibleButton("clickability", dims)) {
ImGui::OpenPopup("sprite_sheet_editor_popup");
}
if (ImGui::BeginPopup("sprite_sheet_editor_popup")) {
SpriteSheetEditor(sheet);
ImGui::EndPopup();
}
}
void LabelSpriteSheet(const char* label, MM::OpenGL::SpriteSheet& sheet) {
const char* lable_display_end = ImGui::FindRenderedTextEnd(label);
ImGui::BeginGroup();
ImGui::PushID(label);
SpriteSheet(sheet);
// drop
if (ImGui::BeginDragDropTarget()) {
if (const ImGuiPayload* payload = ImGui::AcceptDragDropPayload(IMGUI_PAYLOAD_TYPE_MM_REND_TEXTURE)) {
if (payload->DataSize != sizeof(MM::ResourceManager<MM::OpenGL::Texture>::res_id_type)) {
SPDLOG_ERROR("drag'n'drop data size missmatch ({} {})", payload->DataSize, sizeof(MM::ResourceManager<MM::OpenGL::Texture>::res_id_type));
} else {
auto& id = *(MM::ResourceManager<MM::OpenGL::Texture>::res_id_type*)payload->Data;
sheet.tex = MM::ResourceManager<MM::OpenGL::Texture>::ref().get(id);
}
}
ImGui::EndDragDropTarget();
}
ImGui::SameLine();
//const ImVec2 label_size = ImGui::CalcTextSize(label, lable_display_end, true);
//ImGui::Dummy({(w_all - (size.x + label_size.x) + 42.f), s_sz});
if (label != lable_display_end) {
ImGui::SameLine();
ImGui::TextUnformatted(label, lable_display_end);
}
ImGui::PopID(); // pop label
ImGui::EndGroup();
}
void SpriteSheetEditor(MM::OpenGL::SpriteSheet& sheet) {
assert(sheet.tex);
const float w_all = ImGui::CalcItemWidth();
const ImVec2 dims {w_all, w_all * ((float)sheet.tex->height/sheet.tex->width)};
SpriteSheetPreview(sheet, dims);
ImGui::Dummy(dims);
ImGui::Separator();
MM::ImGuiWidgets::LabelTexture("tex##SpriteSheet", sheet.tex);
ImGui::Separator();
uint32_t step = 1;
ImGui::InputScalar("tile_count x##SpriteSheet", ImGuiDataType_U32, &sheet.tile_count.x, &step, NULL, NULL, 0);
ImGui::InputScalar("tile_count y##SpriteSheet", ImGuiDataType_U32, &sheet.tile_count.y, &step, NULL, NULL, 0);
}
} // MM::ImGuiWidgets

View File

@ -0,0 +1,17 @@
#pragma once
// forward
struct ImVec2;
namespace MM::OpenGL {
struct SpriteSheet;
}
namespace MM::ImGuiWidgets {
void SpriteSheetPreview(MM::OpenGL::SpriteSheet& sheet, ImVec2 size);
void SpriteSheet(MM::OpenGL::SpriteSheet& sheet);
void LabelSpriteSheet(const char* label, MM::OpenGL::SpriteSheet& sheet);
void SpriteSheetEditor(MM::OpenGL::SpriteSheet& sheet);
} // MM::ImGuiWidgets

View File

@ -0,0 +1,93 @@
#include "./texture.hpp"
#include <imgui/imgui.h>
#include <imgui/imgui_internal.h>
#include <mm/logger.hpp>
namespace MM::ImGuiWidgets {
// TODO: fix formating
void Texture(MM::OpenGL::Texture::handle& texture, bool dropTarget) {
const float s_sz = ImGui::GetFrameHeight();
//const float w_all = ImGui::CalcItemWidth();
//const char* lable_display_end = ImGui::FindRenderedTextEnd(label);
ImVec2 size{s_sz*2.f, s_sz*2.f};
ImGui::BeginGroup();
if (texture) {
ImGui::Image(texture->getHandle(), size, {0.f, 1.f}, {1.f, 0.f}, ImVec4(1,1,1,1), ImVec4(1,1,1,1));
// "tooltip"
if (ImGui::IsItemHovered()) {
ImGui::BeginTooltip();
const ImVec2 orig_size {(float)texture->width, (float)texture->height};
ImGui::Image(texture->getHandle(), orig_size, {0.f, 1.f}, {1.f, 0.f});
ImGui::EndTooltip();
}
// drag
if (ImGui::BeginDragDropSource(ImGuiDragDropFlags_SourceAllowNullID)) {
auto id = MM::ResourceManager<MM::OpenGL::Texture>::ref().id_from_handle(texture);
if (id) {
ImGui::SetDragDropPayload(IMGUI_PAYLOAD_TYPE_MM_REND_TEXTURE, &(id.value()), sizeof(id.value()));
ImGui::Image(texture->getHandle(), size, {0.f, 1.f}, {1.f, 0.f}, ImVec4(1,1,1,1), ImVec4(1,1,1,1));
}
ImGui::EndDragDropSource();
}
} else {
// TODO: default texture
ImGui::TextUnformatted("NO TEXTURE");
}
// drop
if (dropTarget && ImGui::BeginDragDropTarget()) {
if (const ImGuiPayload* payload = ImGui::AcceptDragDropPayload(IMGUI_PAYLOAD_TYPE_MM_REND_TEXTURE)) {
if (payload->DataSize != sizeof(MM::ResourceManager<MM::OpenGL::Texture>::res_id_type)) {
SPDLOG_ERROR("drag'n'drop data size missmatch ({} {})", payload->DataSize, sizeof(MM::ResourceManager<MM::OpenGL::Texture>::res_id_type));
} else {
auto& id = *(MM::ResourceManager<MM::OpenGL::Texture>::res_id_type*)payload->Data;
texture = MM::ResourceManager<MM::OpenGL::Texture>::ref().get(id);
}
}
ImGui::EndDragDropTarget();
}
ImGui::EndGroup();
}
void LabelTexture(const char* label, MM::OpenGL::Texture::handle& texture, bool dropTarget) {
const float s_sz = ImGui::GetFrameHeight();
const float w_all = ImGui::CalcItemWidth();
const char* lable_display_end = ImGui::FindRenderedTextEnd(label);
ImVec2 size{s_sz*2.f, s_sz*2.f};
if (!texture) {
size = ImGui::CalcTextSize("NO TEXTURE");
}
ImGui::BeginGroup();
ImGui::PushID(label);
Texture(texture, dropTarget);
ImGui::SameLine();
const ImVec2 label_size = ImGui::CalcTextSize(label, lable_display_end, true);
ImGui::Dummy({(w_all - (size.x + label_size.x) + 42.f), s_sz});
if (label != lable_display_end) {
ImGui::SameLine();
ImGui::TextUnformatted(label, lable_display_end);
}
ImGui::PopID(); // pop label
ImGui::EndGroup();
}
}

View File

@ -0,0 +1,13 @@
#pragma once
#include <mm/opengl/texture.hpp>
namespace MM::ImGuiWidgets {
#define IMGUI_PAYLOAD_TYPE_MM_REND_TEXTURE "MM_REND_TEXTURE"
void Texture(MM::OpenGL::Texture::handle& texture, bool dropTarget = true);
void LabelTexture(const char* label, MM::OpenGL::Texture::handle& texture, bool dropTarget = true);
}

Some files were not shown because too many files have changed in this diff Show More