mirror of
https://github.com/MadeOfJelly/MushMachine.git
synced 2025-06-18 18:56:36 +02:00
initial import, >900commits predate this
This commit is contained in:
23
framework/CMakeLists.txt
Normal file
23
framework/CMakeLists.txt
Normal 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()
|
||||
|
28
framework/common_components/CMakeLists.txt
Normal file
28
framework/common_components/CMakeLists.txt
Normal 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()
|
||||
|
||||
|
10
framework/common_components/src/mm/components/color.hpp
Normal file
10
framework/common_components/src/mm/components/color.hpp
Normal 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};
|
||||
};
|
||||
}
|
||||
|
11
framework/common_components/src/mm/components/name.hpp
Normal file
11
framework/common_components/src/mm/components/name.hpp
Normal 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;
|
||||
};
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
16
framework/common_components/src/mm/components/velocity2d.hpp
Normal file
16
framework/common_components/src/mm/components/velocity2d.hpp
Normal 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
|
||||
|
13
framework/common_components/src/mm/components/velocity3d.hpp
Normal file
13
framework/common_components/src/mm/components/velocity3d.hpp
Normal 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
|
||||
|
10
framework/common_components/src/mm/components/view_dir2d.hpp
Normal file
10
framework/common_components/src/mm/components/view_dir2d.hpp
Normal file
@ -0,0 +1,10 @@
|
||||
#pragma once
|
||||
|
||||
namespace MM::Components {
|
||||
|
||||
struct ViewDir2D {
|
||||
float dir = 0.f; // rad
|
||||
};
|
||||
|
||||
} // MM::Components
|
||||
|
12
framework/common_components/src/mm/components/view_dir3d.hpp
Normal file
12
framework/common_components/src/mm/components/view_dir3d.hpp
Normal 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
|
||||
|
16
framework/common_components/test/CMakeLists.txt
Normal file
16
framework/common_components/test/CMakeLists.txt
Normal 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)
|
||||
|
@ -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();
|
||||
}
|
||||
|
34
framework/engine/CMakeLists.txt
Normal file
34
framework/engine/CMakeLists.txt
Normal 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()
|
||||
|
263
framework/engine/src/mm/engine.cpp
Normal file
263
framework/engine/src/mm/engine.cpp
Normal 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
|
||||
|
182
framework/engine/src/mm/engine.hpp
Normal file
182
framework/engine/src/mm/engine.hpp
Normal 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
|
||||
|
12
framework/engine/src/mm/engine_fwd.hpp
Normal file
12
framework/engine/src/mm/engine_fwd.hpp
Normal 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>;
|
||||
}
|
||||
|
52
framework/engine/src/mm/services/default_service.cpp
Normal file
52
framework/engine/src/mm/services/default_service.cpp
Normal 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
|
||||
|
26
framework/engine/src/mm/services/default_service.hpp
Normal file
26
framework/engine/src/mm/services/default_service.hpp
Normal 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
|
||||
|
||||
|
77
framework/engine/src/mm/services/net_channeled_interface.hpp
Normal file
77
framework/engine/src/mm/services/net_channeled_interface.hpp
Normal 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
|
||||
|
27
framework/engine/src/mm/services/scene_service_interface.cpp
Normal file
27
framework/engine/src/mm/services/scene_service_interface.cpp
Normal 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
|
||||
|
44
framework/engine/src/mm/services/scene_service_interface.hpp
Normal file
44
framework/engine/src/mm/services/scene_service_interface.hpp
Normal 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
|
||||
|
24
framework/engine/src/mm/services/service.hpp
Normal file
24
framework/engine/src/mm/services/service.hpp
Normal 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
|
||||
|
16
framework/engine/test/CMakeLists.txt
Normal file
16
framework/engine/test/CMakeLists.txt
Normal 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)
|
||||
|
38
framework/engine/test/default_service_test.cpp
Normal file
38
framework/engine/test/default_service_test.cpp
Normal 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>();
|
||||
}
|
||||
|
61
framework/engine/test/run_test.cpp
Normal file
61
framework/engine/test/run_test.cpp
Normal 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);
|
||||
}
|
||||
|
228
framework/engine/test/service_system_test.cpp
Normal file
228
framework/engine/test/service_system_test.cpp
Normal 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>();
|
||||
}
|
||||
|
230
framework/engine/test/update_test.cpp
Normal file
230
framework/engine/test/update_test.cpp
Normal 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);
|
||||
}
|
||||
|
45
framework/filesystem/CMakeLists.txt
Normal file
45
framework/filesystem/CMakeLists.txt
Normal 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()
|
||||
|
247
framework/filesystem/src/mm/fs_const_archiver.cpp
Normal file
247
framework/filesystem/src/mm/fs_const_archiver.cpp
Normal 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
|
||||
|
42
framework/filesystem/src/mm/fs_const_archiver.hpp
Normal file
42
framework/filesystem/src/mm/fs_const_archiver.hpp
Normal 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 {};
|
||||
|
49
framework/filesystem/src/mm/path_utils.hpp
Normal file
49
framework/filesystem/src/mm/path_utils.hpp
Normal 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
|
||||
|
354
framework/filesystem/src/mm/services/filesystem.cpp
Normal file
354
framework/filesystem/src/mm/services/filesystem.cpp
Normal 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
|
||||
|
89
framework/filesystem/src/mm/services/filesystem.hpp
Normal file
89
framework/filesystem/src/mm/services/filesystem.hpp
Normal 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
|
||||
|
14
framework/filesystem/test/CMakeLists.txt
Normal file
14
framework/filesystem/test/CMakeLists.txt
Normal 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)
|
||||
|
307
framework/filesystem/test/filesystem_tests.cpp
Normal file
307
framework/filesystem/test/filesystem_tests.cpp
Normal 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();
|
||||
}
|
||||
|
BIN
framework/filesystem/test/res/test.zip
Normal file
BIN
framework/filesystem/test/res/test.zip
Normal file
Binary file not shown.
64
framework/filesystem/test/res/test.zip.h
Normal file
64
framework/filesystem/test/res/test.zip.h
Normal 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;
|
1
framework/filesystem/test/res/test_file.txt
Normal file
1
framework/filesystem/test/res/test_file.txt
Normal file
@ -0,0 +1 @@
|
||||
test :D
|
80
framework/filesystem/test/res/wall_concrete-1_se_0.2.json
Normal file
80
framework/filesystem/test/res/wall_concrete-1_se_0.2.json
Normal 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
|
||||
}
|
134
framework/imgui/CMakeLists.txt
Normal file
134
framework/imgui/CMakeLists.txt
Normal 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()
|
||||
|
21
framework/imgui/res/ionicons/LICENSE
Normal file
21
framework/imgui/res/ionicons/LICENSE
Normal 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.
|
BIN
framework/imgui/res/ionicons/ionicons.ttf
Normal file
BIN
framework/imgui/res/ionicons/ionicons.ttf
Normal file
Binary file not shown.
1334
framework/imgui/res/ionicons/ionicons.ttf.base85.h
Normal file
1334
framework/imgui/res/ionicons/ionicons.ttf.base85.h
Normal file
File diff suppressed because it is too large
Load Diff
15712
framework/imgui/res/ionicons/ionicons.ttf.h
Normal file
15712
framework/imgui/res/ionicons/ionicons.ttf.h
Normal file
File diff suppressed because it is too large
Load Diff
130
framework/imgui/src/mm/imgui/file_shader_editor.cpp
Normal file
130
framework/imgui/src/mm/imgui/file_shader_editor.cpp
Normal 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
|
||||
|
30
framework/imgui/src/mm/imgui/file_shader_editor.hpp
Normal file
30
framework/imgui/src/mm/imgui/file_shader_editor.hpp
Normal 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
|
||||
|
236
framework/imgui/src/mm/imgui/file_text_editor.cpp
Normal file
236
framework/imgui/src/mm/imgui/file_text_editor.cpp
Normal 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
|
||||
|
42
framework/imgui/src/mm/imgui/file_text_editor.hpp
Normal file
42
framework/imgui/src/mm/imgui/file_text_editor.hpp
Normal 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
|
||||
|
49
framework/imgui/src/mm/imgui/fps_overlay.cpp
Normal file
49
framework/imgui/src/mm/imgui/fps_overlay.cpp
Normal 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
|
||||
|
12
framework/imgui/src/mm/imgui/fps_overlay.hpp
Normal file
12
framework/imgui/src/mm/imgui/fps_overlay.hpp
Normal file
@ -0,0 +1,12 @@
|
||||
#pragma once
|
||||
|
||||
namespace MM {
|
||||
class ImGuiSimpleFPSOverlay {
|
||||
private:
|
||||
bool _show_plot = false;
|
||||
|
||||
public:
|
||||
void renderImGui(void);
|
||||
};
|
||||
} // namespace MM
|
||||
|
309
framework/imgui/src/mm/imgui/imgui_entt_entity_editor.hpp
Normal file
309
framework/imgui/src/mm/imgui/imgui_entt_entity_editor.hpp
Normal 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([®istry](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.
|
||||
|
33
framework/imgui/src/mm/imgui/sound_info.cpp
Normal file
33
framework/imgui/src/mm/imgui/sound_info.cpp
Normal 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
|
||||
|
8
framework/imgui/src/mm/imgui/sound_info.hpp
Normal file
8
framework/imgui/src/mm/imgui/sound_info.hpp
Normal file
@ -0,0 +1,8 @@
|
||||
#pragma once
|
||||
|
||||
namespace MM {
|
||||
class Engine; // fwd
|
||||
|
||||
void ImGuiSoundInfo(Engine& engine);
|
||||
} // MM
|
||||
|
21
framework/imgui/src/mm/imgui/sound_pref.cpp
Normal file
21
framework/imgui/src/mm/imgui/sound_pref.cpp
Normal 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
|
||||
|
8
framework/imgui/src/mm/imgui/sound_pref.hpp
Normal file
8
framework/imgui/src/mm/imgui/sound_pref.hpp
Normal file
@ -0,0 +1,8 @@
|
||||
#pragma once
|
||||
|
||||
namespace MM {
|
||||
class Engine; // fwd
|
||||
|
||||
void ImGuiSoundPref(Engine& engine);
|
||||
} // MM
|
||||
|
34
framework/imgui/src/mm/imgui/widgets/auto_wrap.hpp
Normal file
34
framework/imgui/src/mm/imgui/widgets/auto_wrap.hpp
Normal 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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
77
framework/imgui/src/mm/imgui/widgets/camera.cpp
Normal file
77
framework/imgui/src/mm/imgui/widgets/camera.cpp
Normal 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();
|
||||
}
|
||||
|
||||
}
|
||||
|
12
framework/imgui/src/mm/imgui/widgets/camera.hpp
Normal file
12
framework/imgui/src/mm/imgui/widgets/camera.hpp
Normal 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);
|
||||
|
||||
}
|
||||
|
21
framework/imgui/src/mm/imgui/widgets/components/color.cpp
Normal file
21
framework/imgui/src/mm/imgui/widgets/components/color.cpp
Normal 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
|
||||
|
15
framework/imgui/src/mm/imgui/widgets/components/color.hpp
Normal file
15
framework/imgui/src/mm/imgui/widgets/components/color.hpp
Normal 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);
|
||||
}
|
||||
|
25
framework/imgui/src/mm/imgui/widgets/components/name.cpp
Normal file
25
framework/imgui/src/mm/imgui/widgets/components/name.cpp
Normal 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
|
||||
|
15
framework/imgui/src/mm/imgui/widgets/components/name.hpp
Normal file
15
framework/imgui/src/mm/imgui/widgets/components/name.hpp
Normal 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);
|
||||
}
|
||||
|
22
framework/imgui/src/mm/imgui/widgets/components/texture.cpp
Normal file
22
framework/imgui/src/mm/imgui/widgets/components/texture.cpp
Normal 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
|
||||
|
17
framework/imgui/src/mm/imgui/widgets/components/texture.hpp
Normal file
17
framework/imgui/src/mm/imgui/widgets/components/texture.hpp
Normal 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);
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
}
|
||||
|
94
framework/imgui/src/mm/imgui/widgets/entity.cpp
Normal file
94
framework/imgui/src/mm/imgui/widgets/entity.cpp
Normal 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);
|
||||
//}
|
||||
|
||||
}
|
||||
|
18
framework/imgui/src/mm/imgui/widgets/entity.hpp
Normal file
18
framework/imgui/src/mm/imgui/widgets/entity.hpp
Normal 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);
|
||||
|
||||
}
|
119
framework/imgui/src/mm/imgui/widgets/filesystem.cpp
Normal file
119
framework/imgui/src/mm/imgui/widgets/filesystem.cpp
Normal 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();
|
||||
}
|
||||
|
||||
}
|
||||
|
15
framework/imgui/src/mm/imgui/widgets/filesystem.hpp
Normal file
15
framework/imgui/src/mm/imgui/widgets/filesystem.hpp
Normal 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);
|
||||
|
||||
}
|
||||
|
79
framework/imgui/src/mm/imgui/widgets/knob.cpp
Normal file
79
framework/imgui/src/mm/imgui/widgets/knob.cpp
Normal 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
|
||||
|
8
framework/imgui/src/mm/imgui/widgets/knob.hpp
Normal file
8
framework/imgui/src/mm/imgui/widgets/knob.hpp
Normal 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);
|
||||
|
||||
}
|
||||
|
192
framework/imgui/src/mm/imgui/widgets/plot_radar.cpp
Normal file
192
framework/imgui/src/mm/imgui/widgets/plot_radar.cpp
Normal 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
|
||||
|
31
framework/imgui/src/mm/imgui/widgets/plot_radar.hpp
Normal file
31
framework/imgui/src/mm/imgui/widgets/plot_radar.hpp
Normal 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
|
||||
|
26
framework/imgui/src/mm/imgui/widgets/soloud.hpp
Normal file
26
framework/imgui/src/mm/imgui/widgets/soloud.hpp
Normal 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
|
||||
|
134
framework/imgui/src/mm/imgui/widgets/soloud_filter.cpp
Normal file
134
framework/imgui/src/mm/imgui/widgets/soloud_filter.cpp
Normal 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
|
||||
|
235
framework/imgui/src/mm/imgui/widgets/soloud_sfxr.cpp
Normal file
235
framework/imgui/src/mm/imgui/widgets/soloud_sfxr.cpp
Normal 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
|
||||
|
117
framework/imgui/src/mm/imgui/widgets/spritesheet.cpp
Normal file
117
framework/imgui/src/mm/imgui/widgets/spritesheet.cpp
Normal 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
|
||||
|
17
framework/imgui/src/mm/imgui/widgets/spritesheet.hpp
Normal file
17
framework/imgui/src/mm/imgui/widgets/spritesheet.hpp
Normal 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
|
||||
|
93
framework/imgui/src/mm/imgui/widgets/texture.cpp
Normal file
93
framework/imgui/src/mm/imgui/widgets/texture.cpp
Normal 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();
|
||||
}
|
||||
|
||||
}
|
||||
|
13
framework/imgui/src/mm/imgui/widgets/texture.hpp
Normal file
13
framework/imgui/src/mm/imgui/widgets/texture.hpp
Normal 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
Reference in New Issue
Block a user