Last active
August 19, 2020 01:00
-
-
Save nikki93/3d63e7eea2d78d351cef46d7025b7eb3 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Save / load | |
template<typename F = bool> | |
auto save(Kernel &ker, F &&filter = true) -> void { | |
// Create document | |
using namespace json; | |
Document root; | |
root.SetObject(); | |
auto &alloc = root.GetAllocator(); | |
// List of types in definition order | |
std::vector types(entt::resolve().begin(), entt::resolve().end()); | |
std::reverse(types.begin(), types.end()); | |
// Entities array | |
auto jEntities = Value(kArrayType); | |
ker.each([&](const Entity ent) { | |
// Check filter | |
if constexpr (std::is_invocable_r_v<bool, decltype(filter), const Entity>) { | |
if (!filter(ent)) { | |
return; | |
} | |
} | |
// Entity object | |
auto jEntity = Value(kObjectType); | |
// Types array | |
auto jTypes = Value(kArrayType); | |
for (auto &type : types) { | |
if (auto typeNameProp = type.prop("name"_hs)) { | |
if (auto hasFn = type.func("_has"_hs)) { | |
if (auto has = hasFn.invoke({}, std::ref(ker), ent); has && has.cast<bool>()) { | |
// Type object | |
auto jType = Value(kObjectType); | |
jType.AddMember("_type", | |
Value().SetString(typeNameProp.value().cast<const char *>(), alloc), alloc); | |
// Get instance of type | |
if (auto getFn = type.func("_get"_hs)) { | |
if (auto inst = getFn.invoke({}, std::ref(ker), ent)) { | |
// Named fields | |
std::vector fields(type.data().begin(), type.data().end()); | |
std::reverse(fields.begin(), fields.end()); | |
for (auto &field : fields) { | |
if (auto fieldNameProp = field.prop("name"_hs)) { | |
auto fieldName = fieldNameProp.value().cast<const char *>(); | |
// Save value | |
auto value = field.get(inst); | |
if (auto p = value.try_cast<double>()) { | |
jType.AddMember(Value().SetString(fieldName, alloc), *p, alloc); | |
} | |
} | |
} | |
// Custom save | |
if (auto saveFn = type.func("save"_hs)) { | |
saveFn.invoke(inst, std::ref(jType), std::ref(alloc)); | |
} | |
} | |
} | |
jTypes.PushBack(jType, alloc); | |
} | |
} | |
} | |
} | |
jEntity.AddMember("types", jTypes, alloc); | |
jEntities.PushBack(jEntity, alloc); | |
}); | |
root.AddMember("entities", jEntities, alloc); | |
// Write buffer | |
StringBuffer buffer; | |
{ | |
PrettyWriter writer(buffer); | |
writer.SetIndent(' ', 2); | |
writer.SetFormatOptions(kFormatSingleLineArray); | |
root.Accept(writer); | |
} | |
#ifdef __EMSCRIPTEN__ | |
emscripten::val::global("window").call<void>("prompt", std::string("Copy scene data..."), | |
std::string(buffer.GetString(), buffer.GetLength())); | |
#endif | |
} | |
auto load(Kernel &ker, Graphics &gfx, const std::string &path) -> void { | |
json::Document root; | |
{ | |
std::ifstream ifs(path); | |
json::BasicIStreamWrapper isw(ifs); | |
root.ParseStream(isw); | |
} | |
for (auto &[sectionName, section] : root.GetObject()) { | |
if (sectionName == "entities") { | |
// Entities array | |
for (auto &jEntity : section.GetArray()) { | |
auto ent = ker.create(); | |
for (auto &[partName, part] : jEntity.GetObject()) { | |
if (partName == "types") { | |
// Types array | |
for (auto &jType : part.GetArray()) { | |
// Type meta | |
auto typeName = jType["_type"].GetString(); | |
if (auto type = entt::resolve_id(entt::hashed_string(typeName))) { | |
if (auto addFn = type.func("add"_hs)) { // Blueprint add | |
addFn.invoke({}, std::ref(ker), ent, std::ref(jType)); | |
} else if (auto addFn = type.func("_add"_hs)) { // Default add | |
addFn.invoke({}, std::ref(ker), ent); | |
} | |
// Check if added | |
if (auto hasFn = type.func("_has"_hs)) { | |
if (auto has = hasFn.invoke({}, std::ref(ker), ent); has && has.cast<bool>()) { | |
// Get instance just added | |
if (auto getFn = type.func("_get"_hs)) { | |
if (auto inst = getFn.invoke({}, std::ref(ker), ent)) { | |
// Named fields | |
for (auto &[fieldName, fieldValue] : jType.GetObject()) { | |
if (auto field | |
= type.data(entt::hashed_string(fieldName.GetString()))) { | |
// Load value | |
if (field.type() == entt::resolve<double>() | |
&& fieldValue.IsDouble()) { | |
field.set(inst, fieldValue.GetDouble()); | |
} | |
} | |
} | |
} | |
} | |
} | |
} | |
} | |
} | |
} | |
} | |
} | |
} | |
} | |
} | |
// Inspector UI | |
auto Edit::inspect() -> void { | |
// Inspector for selected | |
ker.view<Select>().each([&](const Entity ent) { | |
ui.div()("inspector")([&]() { | |
// Callbacks to run after. Prevents the UI from displaying inconsistent states. | |
std::vector<std::function<void(void)>> after; | |
// List of types in definition order | |
std::vector types(entt::resolve().begin(), entt::resolve().end()); | |
std::reverse(types.begin(), types.end()); | |
// Section for each type the entity has | |
for (auto &type : types) { | |
if (auto typeNameProp = type.prop("name"_hs)) { | |
if (auto hasFn = type.func("_has"_hs)) { | |
if (auto has = hasFn.invoke({}, std::ref(ker), ent); has && has.cast<bool>()) { | |
// Lowercase type name | |
auto typeName = std::string(typeNameProp.value().cast<const char *>()); | |
std::transform( | |
typeName.begin(), typeName.end(), typeName.begin(), [](unsigned char c) { | |
return std::tolower(c); | |
}); | |
ui.key(typeName).elem("details")(typeName)("open", true)([&]() { | |
// Section header | |
ui.elem("summary")([&]() { | |
ui.text(typeName); | |
// Remove button | |
ui.button()("remove")("click", [&](emscripten::val) { | |
after.emplace_back([type, &ker = this->ker, ent]() { | |
if (auto removeFn = type.func("_remove"_hs)) { | |
removeFn.invoke({}, std::ref(ker), ent); | |
} | |
}); | |
}); | |
}); | |
// Get instance of type | |
if (auto getFn = type.func("_get"_hs)) { | |
if (auto inst = getFn.invoke({}, std::ref(ker), ent)) { | |
// Custom inspect | |
if (auto inspectFn = type.func("inspect"_hs)) { | |
inspectFn.invoke({}, inst, std::ref(ker), ent); | |
} | |
// Named fields | |
std::vector fields(type.data().begin(), type.data().end()); | |
std::reverse(fields.begin(), fields.end()); | |
for (auto &field : fields) { | |
if (auto fieldNameProp = field.prop("name"_hs)) { | |
auto fieldName = fieldNameProp.value().cast<const char *>(); | |
// Get and display value | |
auto value = field.get(inst); | |
ui.div()("info")([&]() { | |
if (auto p = value.try_cast<double>()) { | |
ui.text("{}: {:.2f}", fieldName, *p); | |
} | |
}); | |
} | |
} | |
} | |
} | |
}); | |
} | |
} | |
} | |
} | |
// Add button for each type the entity doesn't have that can be added | |
ui.div()("add-bar")([&]() { | |
for (auto &type : types) { | |
if (auto typeNameProp = type.prop("name"_hs)) { | |
if (auto hasFn = type.func("_has"_hs), addFn = type.func("_add"_hs); hasFn && addFn) { | |
if (auto has = hasFn.invoke({}, std::ref(ker), ent); has && !has.cast<bool>()) { | |
// Add button with (lowercase) type name | |
auto typeName = std::string(typeNameProp.value().cast<const char *>()); | |
std::transform( | |
typeName.begin(), typeName.end(), typeName.begin(), [](unsigned char c) { | |
return std::tolower(c); | |
}); | |
ui.button()("add")("label", typeName)("click", [&](emscripten::val) { | |
addFn.invoke({}, std::ref(ker), ent); | |
}); | |
} | |
} | |
} | |
} | |
}); | |
// Run the after callbacks | |
for (auto &func : after) { | |
func(); | |
} | |
}); | |
}); | |
} | |
// Custom inspectors for `Sprite` and `Feet` | |
auto Sprite_inspect(Sprite &spr, Kernel &ker, const Entity) -> void { | |
auto &ui = ker.ctx<UI>(); | |
if (static auto firstFrame = true; !firstFrame) { // Workaround for Safari | |
ui.elem("img")("preview")("src", spr.image.getBlobUrl()); | |
} else { | |
firstFrame = false; | |
} | |
ui.div()("info")([&]() { | |
ui.text("path: {}", spr.image.getPath()); | |
}); | |
ui.div()("info")([&]() { | |
auto [imgW, imgH] = spr.image.getSize(); | |
ui.text("width: {}, height: {}", imgW, imgH); | |
}); | |
} | |
ngMethod(Sprite, inspect); | |
auto Feet_inspect(Feet &feet, Kernel &ker, const Entity ent) -> void { | |
auto &ui = ker.ctx<UI>(); | |
auto &edit = ker.ctx<Edit>(); | |
ui.div()("info")([&]() { | |
ui.text("shape: {} vertices", feet.shape.getNumVertices()); | |
if (!ker.has<Player>(ent)) { | |
ui.button()("shape")("selected", edit.getMode() == "shape")("click", [&](emscripten::val) { | |
edit.setEnabled(true); | |
edit.setMode(edit.getMode() == "shape" ? "select" : "shape"); | |
}); | |
} | |
}); | |
} | |
ngMethod(Feet, inspect); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#pragma once | |
#include "precomp.h" | |
#include "timing.h" | |
using Entity = entt::entity; | |
struct Kernel { | |
Kernel(const Kernel &) = delete; | |
auto operator=(const Kernel &) -> Kernel & = delete; | |
Kernel(const Kernel &&) = delete; | |
auto operator=(const Kernel &&) -> Kernel & = delete; | |
explicit Kernel(Timing &tim_) | |
: tim(tim_) { | |
} | |
~Kernel() = default; | |
Timing &tim; | |
inline static constexpr Entity null = entt::null; | |
// Create / destroy | |
auto create() -> Entity { | |
return ents.create(); | |
} | |
auto destroy(const Entity ent) -> void { | |
ents.destroy(ent); | |
} | |
// Add / remove | |
template<typename T, typename... Args> | |
decltype(auto) add(const Entity ent, Args &&... args) { | |
if constexpr (canInvokeAdd<T, Kernel &, const Entity, Args...>) { | |
T::add(*this, ent, std::forward<Args>(args)...); | |
if constexpr (!std::is_empty_v<T>) { | |
return get<T>(ent); | |
} | |
} else { | |
return ents.emplace<T>(ent, std::forward<Args>(args)...); | |
} | |
} | |
template<typename... Ts> | |
auto remove(const Entity ent) -> void { | |
ents.remove<Ts...>(ent); | |
} | |
template<typename... Ts> | |
auto clear() -> void { | |
ents.clear<Ts...>(); | |
} | |
// Has / get | |
template<typename... Ts> | |
auto has(const Entity ent) -> bool { | |
return ents.has<Ts...>(ent); | |
} | |
template<typename... Ts> | |
decltype(auto) get(const Entity ent) { | |
return ents.get<Ts...>(ent); | |
} | |
// Queries | |
template<typename... Ts> | |
using Exclude = entt::exclude_t<Ts...>; | |
template<typename... Ts> | |
inline static auto exclude = entt::exclude<Ts...>; | |
template<typename... Ts, typename... Es> | |
auto view(Exclude<Es...> = {}) const { | |
return ents.view<Ts...>(exclude<Es>...); | |
} | |
template<typename... Ts, typename... Es> | |
auto view(Exclude<Es...> = {}) { | |
return ents.view<Ts...>(exclude<Es>...); | |
} | |
template<typename F> | |
auto each(F &&f) -> void { | |
ents.each(std::forward<F>(f)); | |
} | |
// Sort | |
template<typename T, typename Compare, typename... Args> | |
auto isort(Compare compare, Args &&... args) -> void { | |
ents.sort<T>(std::move(compare), entt::insertion_sort(), std::forward<Args>(args)...); | |
} | |
// Rules | |
template<typename... Args> | |
struct Trigger { | |
using Handler = void (*)(Kernel &, Args...); | |
}; | |
template<typename T, typename F> | |
static auto addRule(const char *name, F &&f) -> bool { | |
rules<T>.push_back(Rule<T> { name, f }); | |
return true; | |
} | |
#define ngRule(T, name) \ | |
extern T::Handler name##Handler; \ | |
auto name##HandlerRegistered = Kernel::addRule<T>(#name, name##Handler); \ | |
T::Handler name##Handler = +[] | |
template<typename T, typename... Args> | |
auto run(Args &&... args) -> void { | |
tim.prof(describe<T>(), [&]() { | |
for (auto &rule : rules<T>) { | |
tim.prof(rule.name, [&]() { | |
rule.handler(*this, std::forward<Args>(args)...); | |
}); | |
} | |
}); | |
} | |
// Context | |
template<typename T> | |
auto ctx() -> T & { | |
return *(static_cast<T *>(context[contextId<T>])); | |
} | |
template<typename T, typename U, typename... Vs> | |
auto ctx() -> std::tuple<T &, U &, Vs &...> { | |
return { ctx<T>(), ctx<U>(), ctx<Vs>()... }; | |
} | |
template<typename... Ts> | |
auto ctx(Ts &... vs) -> void { | |
((context[contextId<Ts>] = &vs), ...); | |
} | |
private: | |
template<typename T, typename... Args> | |
static auto _canInvokeAdd(int) -> decltype(T::add(std::declval<Args>()...), std::true_type()); | |
template<typename T, typename... Args> | |
static auto _canInvokeAdd(...) -> std::false_type; | |
template<typename T, typename... Args> | |
inline static constexpr auto canInvokeAdd | |
= std::is_same_v<decltype(_canInvokeAdd<T, Args...>(0)), std::true_type>; | |
entt::registry ents; | |
template<typename T> | |
struct Rule { | |
std::string name; | |
typename T::Handler handler; | |
}; | |
template<typename T> | |
static std::vector<Rule<T>> rules; | |
static constexpr auto contextSize = 16; | |
inline static int nextContextId = 0; | |
template<typename T> | |
static const int contextId; | |
std::array<void *, contextSize> context = {}; | |
static auto demangle(const char *sym) -> std::string; | |
template<typename T> | |
static auto describe() -> const std::string & { | |
static auto result = demangle(typeid(T).name()); | |
return result; | |
} | |
// Meta | |
public: | |
template<typename T> | |
struct Meta { | |
static auto add(Kernel &ker, const Entity ent) -> void { | |
ker.add<T>(ent); | |
} | |
static auto remove(Kernel &ker, const Entity ent) -> void { | |
ker.remove<T>(ent); | |
} | |
static auto clear(Kernel &ker) -> void { | |
ker.clear<T>(); | |
} | |
static auto has(Kernel &ker, const Entity ent) -> bool { | |
return ker.has<T>(ent); | |
} | |
static decltype(auto) get(Kernel &ker, const Entity ent) { | |
return ker.get<T>(ent); | |
} | |
static auto bind() -> bool { | |
auto meta = entt::meta<T>(); | |
if (canInvokeAdd<T, Kernel &, const Entity> || std::is_default_constructible_v<T>) { | |
meta.template func<&add>("_add"_hs); | |
} | |
meta.template func<&remove>("_remove"_hs); | |
meta.template func<&clear>("_clear"_hs); | |
meta.template func<&has>("_has"_hs); | |
if constexpr (!std::is_empty_v<T>) { | |
meta.template func<&get, entt::as_ref_t>("_get"_hs); | |
} | |
return true; | |
} | |
#define __ngMany(_1, _2, _3, _4, selected, ...) selected | |
#define _ngMany(name, ...) \ | |
__ngMany(__VA_ARGS__, name##4, name##3, name##2, name##1, unused)(__VA_ARGS__) | |
#define ngType(T) \ | |
using Type = T; \ | |
inline static auto meta = entt::meta<Type>().type(#T##_hs).prop("name"_hs, #T); \ | |
inline static auto boundMeta = Kernel::Meta<Type>::bind() | |
#define ngFieldMeta(name, ...) \ | |
inline static auto name##Meta \ | |
= meta.data<__VA_ARGS__, entt::as_ref_t>(#name##_hs).prop("name"_hs, #name) | |
#define ngField1(name) ngFieldMeta(name, &Type::name) | |
#define ngField2(type, name) \ | |
type name; \ | |
ngFieldMeta(name, &Type::name) | |
#define ngField3(type, name, def) \ | |
type name = def; \ | |
ngFieldMeta(name, &Type::name) | |
#define ngField4(type, name, getter, setter) ngFieldMeta(name, &Type::setter, &Type::getter) | |
#define ngField(...) _ngMany(ngField, __VA_ARGS__) | |
#define ngMethod1(name) inline static auto name##Meta = meta.func<&Type::name>(#name##_hs) | |
#define ngMethod2(T, name) auto T##name##Meta = T::meta.func<(&T##_##name)>(#name##_hs) | |
#define ngMethod(...) _ngMany(ngMethod, __VA_ARGS__) | |
}; | |
}; | |
template<typename T> | |
inline std::vector<Kernel::Rule<T>> Kernel::rules; | |
template<typename T> | |
inline const int Kernel::contextId = []() { | |
assert(nextContextId < contextSize); | |
return nextContextId++; | |
}(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment