Skip to content

Instantly share code, notes, and snippets.

@nikki93
Last active August 19, 2020 01:00
Show Gist options
  • Save nikki93/3d63e7eea2d78d351cef46d7025b7eb3 to your computer and use it in GitHub Desktop.
Save nikki93/3d63e7eea2d78d351cef46d7025b7eb3 to your computer and use it in GitHub Desktop.
// 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);
#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