deps: add flecs-dash
|
@ -19,6 +19,12 @@ add_executable(eco2d-server
|
|||
header/components/general.h
|
||||
header/components/controllers.h
|
||||
|
||||
../../common/signal_handling.c
|
||||
../../common/signal_handling.h
|
||||
|
||||
../../common/assets.h
|
||||
../../common/assets.c
|
||||
|
||||
../../vendors/cwpack/cwpack.c
|
||||
../../vendors/cwpack/cwpack.h
|
||||
|
||||
|
@ -26,12 +32,22 @@ add_executable(eco2d-server
|
|||
../../vendors/flecs/flecs.h
|
||||
../../vendors/flecs/flecs_meta.c
|
||||
../../vendors/flecs/flecs_meta.h
|
||||
|
||||
../../common/signal_handling.c
|
||||
../../common/signal_handling.h
|
||||
|
||||
../../common/assets.h
|
||||
../../common/assets.c
|
||||
../../vendors/flecs/flecs_json.h
|
||||
../../vendors/flecs/flecs_json.c
|
||||
../../vendors/flecs/flecs_rest.h
|
||||
../../vendors/flecs/flecs_rest.c
|
||||
../../vendors/flecs/flecs_monitor.h
|
||||
../../vendors/flecs/flecs_monitor.c
|
||||
../../vendors/flecs/flecs_player.h
|
||||
../../vendors/flecs/flecs_player.c
|
||||
../../vendors/flecs/flecs_dash.h
|
||||
../../vendors/flecs/flecs_dash.c
|
||||
../../vendors/flecs/flecs_components_http.h
|
||||
../../vendors/flecs/flecs_components_http.c
|
||||
../../vendors/flecs/flecs_systems_civetweb.h
|
||||
../../vendors/flecs/flecs_systems_civetweb.c
|
||||
../../vendors/flecs/flecs_os_api_posix.h
|
||||
../../vendors/flecs/flecs_os_api_posix.c
|
||||
)
|
||||
|
||||
include_directories(eco2d-server header)
|
||||
|
|
|
@ -7,6 +7,11 @@
|
|||
#include "utils/options.h"
|
||||
#include "signal_handling.h"
|
||||
|
||||
#include "flecs/flecs.h"
|
||||
#include "flecs/flecs_dash.h"
|
||||
#include "flecs/flecs_systems_civetweb.h"
|
||||
#include "flecs/flecs_os_api_posix.h"
|
||||
|
||||
#define DEFAULT_WORLD_SEED 302097
|
||||
#define DEFAULT_BLOCK_SIZE 64 /* amount of units within a block (single axis) */
|
||||
#define DEFAULT_CHUNK_SIZE 3 /* amount of blocks within a chunk (single axis) */
|
||||
|
@ -59,10 +64,19 @@ int main(int argc, char** argv) {
|
|||
}
|
||||
|
||||
sighandler_register();
|
||||
posix_set_os_api();
|
||||
|
||||
zpl_printf("[INFO] Generating world of size: %d x %d\n", world_size, world_size);
|
||||
IF(world_init(seed, block_size, chunk_size, world_size));
|
||||
|
||||
/* server dashboard */
|
||||
{
|
||||
ECS_IMPORT(world_ecs(), FlecsDash);
|
||||
ECS_IMPORT(world_ecs(), FlecsSystemsCivetweb);
|
||||
|
||||
ecs_set(world_ecs(), 0, EcsDashServer, {.port = 27001});
|
||||
}
|
||||
|
||||
zpl_printf("[INFO] Initializing network...\n");
|
||||
IF(network_init());
|
||||
IF(network_server_start("0.0.0.0", 27000));
|
||||
|
|
|
@ -70,6 +70,8 @@ int32_t world_init(int32_t seed, uint16_t block_size, uint16_t chunk_size, uint1
|
|||
|
||||
world.ecs = ecs_init();
|
||||
ecs_set_entity_range(world.ecs, 0, UINT32_MAX);
|
||||
ecs_set_threads(world.ecs, 4);
|
||||
ecs_set_target_fps(world.ecs, 60);
|
||||
|
||||
world.tracker = librg_world_create();
|
||||
|
||||
|
|
|
@ -0,0 +1,94 @@
|
|||
#ifndef FLECS_COMPONENTS_HTTP_IMPL
|
||||
#include "flecs_components_http.h"
|
||||
#endif
|
||||
|
||||
static
|
||||
void decode(
|
||||
char *str)
|
||||
{
|
||||
char ch, *ptr, *dst = str;
|
||||
|
||||
for (ptr = str; (ch = *ptr); ptr++) {
|
||||
if (ch == '%') {
|
||||
if (ptr[1] == '2' && ptr[2] == '0') {
|
||||
dst[0] = ' ';
|
||||
dst ++;
|
||||
ptr += 2;
|
||||
}
|
||||
} else {
|
||||
dst[0] = ptr[0];
|
||||
dst ++;
|
||||
}
|
||||
}
|
||||
|
||||
dst[0] = '\0';
|
||||
}
|
||||
|
||||
size_t ecs_http_get_query_param(
|
||||
const char *query,
|
||||
const char *name,
|
||||
char *buffer,
|
||||
size_t len)
|
||||
{
|
||||
if (!query) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const char *cur, *start = query, *value = NULL;
|
||||
char ch;
|
||||
int arg_len = strlen(name);
|
||||
|
||||
for (cur = query; (ch = *cur); cur ++) {
|
||||
if (ch == '=') {
|
||||
if (cur - start >= 256) {
|
||||
/* Ignore, key is too long */
|
||||
} else {
|
||||
int32_t key_len = cur - start;
|
||||
cur ++;
|
||||
value = cur;
|
||||
while ((ch = *cur) && ch != '&') {
|
||||
cur ++;
|
||||
}
|
||||
|
||||
if (!ch || ch == '&') {
|
||||
/* Check if requested key matches */
|
||||
if (arg_len == key_len && !strncmp(name, start, key_len)) {
|
||||
size_t size;
|
||||
if (cur - value < (int32_t)len) {
|
||||
size = cur - value;
|
||||
} else {
|
||||
size = len - 1;
|
||||
}
|
||||
|
||||
memcpy(buffer, value, size);
|
||||
buffer[size] = '\0';
|
||||
|
||||
decode(buffer);
|
||||
|
||||
return cur - value;
|
||||
}
|
||||
}
|
||||
|
||||
start = cur + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void FlecsComponentsHttpImport(
|
||||
ecs_world_t *world)
|
||||
{
|
||||
ECS_MODULE(world, FlecsComponentsHttp);
|
||||
|
||||
ECS_IMPORT(world, FlecsMeta);
|
||||
|
||||
ecs_set_name_prefix(world, "EcsHttp");
|
||||
|
||||
ECS_META(world, EcsHttpServer);
|
||||
ECS_META(world, EcsHttpEndpoint);
|
||||
|
||||
ECS_SET_COMPONENT(EcsHttpServer);
|
||||
ECS_SET_COMPONENT(EcsHttpEndpoint);
|
||||
}
|
|
@ -0,0 +1,121 @@
|
|||
#define flecs_components_http_STATIC
|
||||
#ifndef FLECS_COMPONENTS_HTTP_H
|
||||
#define FLECS_COMPONENTS_HTTP_H
|
||||
|
||||
/*
|
||||
)
|
||||
(.)
|
||||
.|.
|
||||
| |
|
||||
_.--| |--._
|
||||
.-'; ;`-'& ; `&.
|
||||
\ & ; & &_/
|
||||
|"""---...---"""|
|
||||
\ | | | | | | | /
|
||||
`---.|.|.|.---'
|
||||
|
||||
* This file is generated by bake.lang.c for your convenience. Headers of
|
||||
* dependencies will automatically show up in this file. Include bake_config.h
|
||||
* in your main project file. Do not edit! */
|
||||
|
||||
#ifndef FLECS_COMPONENTS_HTTP_BAKE_CONFIG_H
|
||||
#define FLECS_COMPONENTS_HTTP_BAKE_CONFIG_H
|
||||
|
||||
/* Headers of public dependencies */
|
||||
#include <flecs.h>
|
||||
#include <flecs_meta.h>
|
||||
|
||||
/* Convenience macro for exporting symbols */
|
||||
#ifndef flecs_components_http_STATIC
|
||||
#if flecs_components_http_EXPORTS && (defined(_MSC_VER) || defined(__MINGW32__))
|
||||
#define FLECS_COMPONENTS_HTTP_API __declspec(dllexport)
|
||||
#elif flecs_components_http_EXPORTS
|
||||
#define FLECS_COMPONENTS_HTTP_API __attribute__((__visibility__("default")))
|
||||
#elif defined _MSC_VER
|
||||
#define FLECS_COMPONENTS_HTTP_API __declspec(dllimport)
|
||||
#else
|
||||
#define FLECS_COMPONENTS_HTTP_API
|
||||
#endif
|
||||
#else
|
||||
#define FLECS_COMPONENTS_HTTP_API
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
ECS_ENUM(EcsHttpMethod, {
|
||||
EcsHttpGet,
|
||||
EcsHttpPost,
|
||||
EcsHttpPut,
|
||||
EcsHttpDelete,
|
||||
EcsHttpMethodUnknown
|
||||
});
|
||||
|
||||
struct EcsHttpEndpoint;
|
||||
|
||||
typedef struct EcsHttpRequest {
|
||||
const char *url;
|
||||
const char *relative_url;
|
||||
const char *params;
|
||||
void *ctx;
|
||||
EcsHttpMethod method;
|
||||
ecs_entity_t server;
|
||||
} EcsHttpRequest;
|
||||
|
||||
ECS_STRUCT(EcsHttpReply, {
|
||||
char *header;
|
||||
char *body;
|
||||
int status;
|
||||
bool is_file;
|
||||
});
|
||||
|
||||
typedef bool (*EcsHttpServiceAction)(
|
||||
ecs_world_t *world,
|
||||
ecs_entity_t entity,
|
||||
struct EcsHttpEndpoint *endpoint,
|
||||
EcsHttpRequest *request,
|
||||
EcsHttpReply *reply);
|
||||
|
||||
ECS_STRUCT(EcsHttpServer, {
|
||||
uint16_t port;
|
||||
});
|
||||
|
||||
ECS_STRUCT(EcsHttpEndpoint, {
|
||||
char *url;
|
||||
void *ctx;
|
||||
bool synchronous;
|
||||
|
||||
ECS_PRIVATE
|
||||
EcsHttpServiceAction action;
|
||||
});
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef struct FlecsComponentsHttp {
|
||||
ECS_DECLARE_COMPONENT(EcsHttpServer);
|
||||
ECS_DECLARE_COMPONENT(EcsHttpEndpoint);
|
||||
} FlecsComponentsHttp;
|
||||
|
||||
FLECS_COMPONENTS_HTTP_API
|
||||
void FlecsComponentsHttpImport(
|
||||
ecs_world_t *world);
|
||||
|
||||
/* Utility to get parameter from query string */
|
||||
FLECS_COMPONENTS_HTTP_API
|
||||
size_t ecs_http_get_query_param(
|
||||
const char *query,
|
||||
const char *name,
|
||||
char *buffer,
|
||||
size_t len);
|
||||
|
||||
#define FlecsComponentsHttpImportHandles(handles)\
|
||||
ECS_IMPORT_COMPONENT(handles, EcsHttpServer);\
|
||||
ECS_IMPORT_COMPONENT(handles, EcsHttpEndpoint);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
|
@ -0,0 +1,271 @@
|
|||
#ifndef FLECS_DASH_IMPL
|
||||
#include "flecs_dash.h"
|
||||
#endif
|
||||
|
||||
|
||||
static
|
||||
void EcsUpdateFps(ecs_iter_t *it) {
|
||||
ecs_world_t *world = it->world;
|
||||
|
||||
EcsFps *world_fps = ecs_column(it, EcsFps, 1);
|
||||
|
||||
const ecs_world_info_t *info = ecs_get_world_info(world);
|
||||
world_fps->value = 1.0 / info->delta_time_raw;
|
||||
world_fps->count += world_fps->value;
|
||||
world_fps->target = info->target_fps;
|
||||
}
|
||||
|
||||
static
|
||||
void EcsUpdateLoad(ecs_iter_t *it) {
|
||||
ecs_world_t *world = it->world;
|
||||
|
||||
EcsLoad *world_load = ecs_column(it, EcsLoad, 1);
|
||||
|
||||
const ecs_world_info_t *info = ecs_get_world_info(world);
|
||||
|
||||
world_load->total_time_count += info->delta_time_raw;
|
||||
world_load->frame_time_count = info->frame_time_total;
|
||||
world_load->system_time_count = info->system_time_total;
|
||||
world_load->merge_time_count = info->merge_time_total;
|
||||
}
|
||||
|
||||
static
|
||||
void EcsUpdateTick(ecs_iter_t *it) {
|
||||
ecs_world_t *world = it->world;
|
||||
|
||||
EcsTick *world_tick = ecs_column(it, EcsTick, 1);
|
||||
|
||||
const ecs_world_info_t *info = ecs_get_world_info(world);
|
||||
world_tick->count = info->frame_count_total;
|
||||
}
|
||||
|
||||
static
|
||||
void EcsUpdateClock(ecs_iter_t *it) {
|
||||
ecs_world_t *world = it->world;
|
||||
|
||||
EcsWorldClock *world_clock = ecs_column(it, EcsWorldClock, 1);
|
||||
|
||||
const ecs_world_info_t *info = ecs_get_world_info(world);
|
||||
world_clock->world_time = info->world_time_total;
|
||||
world_clock->world_time_raw = 0;
|
||||
}
|
||||
|
||||
void FlecsDashMonitorImport(
|
||||
ecs_world_t *world)
|
||||
{
|
||||
ECS_MODULE(world, FlecsDashMonitor);
|
||||
|
||||
ECS_IMPORT(world, FlecsMeta);
|
||||
|
||||
ecs_set_name_prefix(world, "Ecs");
|
||||
|
||||
ECS_META(world, EcsFps);
|
||||
ECS_META(world, EcsLoad);
|
||||
ECS_META(world, EcsTick);
|
||||
ECS_META(world, EcsWorldClock);
|
||||
|
||||
/* System for keeping track of world stats */
|
||||
ecs_set(world, EcsWorld, EcsFps, {0});
|
||||
ECS_SYSTEM(world, EcsUpdateFps, EcsOnLoad, flecs.core.World:Fps);
|
||||
|
||||
ecs_set(world, EcsWorld, EcsLoad, {0});
|
||||
ECS_SYSTEM(world, EcsUpdateLoad, EcsOnLoad, flecs.core.World:Load);
|
||||
|
||||
ecs_set(world, EcsWorld, EcsTick, {0});
|
||||
ECS_SYSTEM(world, EcsUpdateTick, EcsOnLoad, flecs.core.World:Tick);
|
||||
|
||||
ecs_set(world, EcsWorld, EcsWorldClock, {0});
|
||||
ECS_SYSTEM(world, EcsUpdateClock, EcsOnLoad, flecs.core.World:WorldClock);
|
||||
}
|
||||
|
||||
static
|
||||
bool request_this(
|
||||
ecs_world_t *world,
|
||||
ecs_entity_t entity,
|
||||
EcsHttpEndpoint *endpoint,
|
||||
EcsHttpRequest *request,
|
||||
EcsHttpReply *reply)
|
||||
{
|
||||
ecs_entity_t server = ecs_get_parent_w_entity(world, entity, 0);
|
||||
if (server) {
|
||||
ecs_strbuf_t buf = ECS_STRBUF_INIT;
|
||||
char *path = ecs_get_fullpath(world, server);
|
||||
ecs_strbuf_append(&buf, "{\"server_id\":\"%s\"}", path);
|
||||
ecs_os_free(path);
|
||||
reply->body = ecs_strbuf_get(&buf);
|
||||
return true;
|
||||
} else {
|
||||
reply->body = ecs_os_strdup("{\"server_id\":\"unknown\"}");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static
|
||||
bool request_files(
|
||||
ecs_world_t *world,
|
||||
ecs_entity_t entity,
|
||||
EcsHttpEndpoint *endpoint,
|
||||
EcsHttpRequest *request,
|
||||
EcsHttpReply *reply)
|
||||
{
|
||||
const char *file = request->relative_url;
|
||||
char path[1024];
|
||||
|
||||
if (!file || !strlen(file)) {
|
||||
file = "index.html";
|
||||
}
|
||||
|
||||
char *etc_path = ecs_os_module_to_etc("flecs.dash");
|
||||
|
||||
sprintf(path, "%s/%s", etc_path, file);
|
||||
|
||||
FILE *f = fopen(path, "r");
|
||||
if (!f) {
|
||||
return false;
|
||||
} else {
|
||||
fclose(f);
|
||||
}
|
||||
|
||||
reply->body = ecs_os_strdup(path);
|
||||
reply->is_file = true;
|
||||
|
||||
ecs_os_free(etc_path);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static
|
||||
bool request_player(
|
||||
ecs_world_t *world,
|
||||
ecs_entity_t entity,
|
||||
EcsHttpEndpoint *endpoint,
|
||||
EcsHttpRequest *request,
|
||||
EcsHttpReply *reply)
|
||||
{
|
||||
const char *cmd = request->relative_url;
|
||||
|
||||
ecs_entity_t ecs_typeid(EcsPlayer) = ecs_lookup_fullpath(
|
||||
world, "flecs.player.Player");
|
||||
|
||||
if (ecs_typeid(EcsPlayer)) {
|
||||
EcsPlayer *player = ecs_get_mut(world, EcsWorld, EcsPlayer, NULL);
|
||||
|
||||
if (!strcmp(cmd, "play")) {
|
||||
player->state = EcsPlayerPlay;
|
||||
} else if (!strcmp(cmd, "pause")) {
|
||||
player->state = EcsPlayerPause;
|
||||
} else if (!strcmp(cmd, "stop")) {
|
||||
player->state = EcsPlayerStop;
|
||||
}
|
||||
|
||||
ecs_modified(world, EcsWorld, EcsPlayer);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static
|
||||
void RunServer(ecs_iter_t *it) {
|
||||
ecs_world_t *world = it->world;
|
||||
|
||||
EcsDashServer *server = ecs_column(it, EcsDashServer, 1);
|
||||
|
||||
ecs_entity_t ecs_typeid(EcsHttpEndpoint) = ecs_column_entity(it, 2);
|
||||
ecs_entity_t ecs_typeid(EcsRestServer) = ecs_column_entity(it, 3);
|
||||
ecs_entity_t ecs_typeid(EcsMonitorServer) = ecs_column_entity(it, 4);
|
||||
ecs_entity_t ecs_typeid(EcsDashApp) = ecs_column_entity(it, 5);
|
||||
ecs_entity_t EcsDashInitialized = ecs_column_entity(it, 6);
|
||||
|
||||
int32_t i;
|
||||
for (i = 0; i < it->count; i ++) {
|
||||
ecs_entity_t e = it->entities[i];
|
||||
EcsDashServer *s = &server[i];
|
||||
|
||||
/* Create REST & monitor server */
|
||||
ecs_set(world, e, EcsRestServer, {.port = s->port});
|
||||
ecs_set(world, e, EcsMonitorServer, {.port = s->port});
|
||||
|
||||
if (ecs_has_entity(world, e, EcsDashInitialized)) {
|
||||
/* Don't add endpoints again if already initialized */
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Add endpoint to server for serving up files */
|
||||
ecs_entity_t e_files = ecs_new_w_entity(world, ECS_CHILDOF | e);
|
||||
ecs_set(world, e_files, EcsName, {"e_files"});
|
||||
ecs_set(world, e_files, EcsHttpEndpoint, {
|
||||
.url = "",
|
||||
.action = request_files});
|
||||
|
||||
/* Add endpoint to server that returns entity id of server */
|
||||
ecs_entity_t e_this = ecs_new_w_entity(world, ECS_CHILDOF | e);
|
||||
ecs_set(world, e_this, EcsName, {"e_this"});
|
||||
ecs_set(world, e_this, EcsHttpEndpoint, {
|
||||
.url = "this",
|
||||
.action = request_this});
|
||||
|
||||
/* Add endpoint to server that returns entity id of server */
|
||||
ecs_entity_t e_player = ecs_new_w_entity(world, ECS_CHILDOF | e);
|
||||
ecs_set(world, e_player, EcsName, {"e_player"});
|
||||
ecs_set(world, e_player, EcsHttpEndpoint, {
|
||||
.url = "player",
|
||||
.action = request_player,
|
||||
.synchronous = true});
|
||||
|
||||
/* Add browser app */
|
||||
ecs_entity_t dash_overview = ecs_new_w_entity(world, ECS_CHILDOF | e);
|
||||
ecs_set(world, dash_overview, EcsName, {"overview"});
|
||||
ecs_set(world, dash_overview, EcsDashApp, {
|
||||
.path = "etc/apps/overview",
|
||||
.icon = "images/usage.png"
|
||||
});
|
||||
|
||||
ecs_entity_t dash_systems = ecs_new_w_entity(world, ECS_CHILDOF | e);
|
||||
ecs_set(world, dash_systems, EcsName, {"systems"});
|
||||
ecs_set(world, dash_systems, EcsDashApp, {
|
||||
.path = "etc/apps/systems",
|
||||
.icon = "images/layers.png"
|
||||
});
|
||||
|
||||
ecs_entity_t dash_browser = ecs_new_w_entity(world, ECS_CHILDOF | e);
|
||||
ecs_set(world, dash_browser, EcsName, {"browser"});
|
||||
ecs_set(world, dash_browser, EcsDashApp, {
|
||||
.path = "etc/apps/browser",
|
||||
.icon = "images/tree.png"
|
||||
});
|
||||
|
||||
/* Prevent initializing the server again */
|
||||
ecs_add_entity(world, e, EcsDashInitialized);
|
||||
}
|
||||
}
|
||||
|
||||
void FlecsDashImport(
|
||||
ecs_world_t *world)
|
||||
{
|
||||
ECS_MODULE(world, FlecsDash);
|
||||
|
||||
ECS_IMPORT(world, FlecsMonitor);
|
||||
ECS_IMPORT(world, FlecsDashMonitor);
|
||||
|
||||
ECS_IMPORT(world, FlecsMeta);
|
||||
ECS_IMPORT(world, FlecsPlayer);
|
||||
ECS_IMPORT(world, FlecsComponentsHttp);
|
||||
ECS_IMPORT(world, FlecsRest);
|
||||
|
||||
ecs_set_name_prefix(world, "EcsDash");
|
||||
|
||||
ECS_META(world, EcsDashServer);
|
||||
ECS_META(world, EcsDashApp);
|
||||
|
||||
ECS_TAG(world, EcsDashInitialized);
|
||||
|
||||
ECS_SYSTEM(world, RunServer, EcsOnSet, Server,
|
||||
:flecs.components.http.Endpoint,
|
||||
:flecs.rest.Server,
|
||||
:flecs.monitor.Server,
|
||||
:App,
|
||||
:Initialized);
|
||||
|
||||
ECS_EXPORT_COMPONENT(EcsDashServer);
|
||||
ECS_EXPORT_COMPONENT(EcsDashApp);
|
||||
}
|
|
@ -0,0 +1,158 @@
|
|||
#define flecs_dash_STATIC
|
||||
#ifndef FLECS_DASH_H
|
||||
#define FLECS_DASH_H
|
||||
|
||||
/* This generated file contains includes for project dependencies */
|
||||
/*
|
||||
)
|
||||
(.)
|
||||
.|.
|
||||
| |
|
||||
_.--| |--._
|
||||
.-'; ;`-'& ; `&.
|
||||
\ & ; & &_/
|
||||
|"""---...---"""|
|
||||
\ | | | | | | | /
|
||||
`---.|.|.|.---'
|
||||
|
||||
* This file is generated by bake.lang.c for your convenience. Headers of
|
||||
* dependencies will automatically show up in this file. Include bake_config.h
|
||||
* in your main project file. Do not edit! */
|
||||
|
||||
#ifndef FLECS_DASH_BAKE_CONFIG_H
|
||||
#define FLECS_DASH_BAKE_CONFIG_H
|
||||
|
||||
/* Headers of public dependencies */
|
||||
#include <flecs.h>
|
||||
#include <flecs_meta.h>
|
||||
#include <flecs_player.h>
|
||||
#include <flecs_components_http.h>
|
||||
#include <flecs_rest.h>
|
||||
#include <flecs_monitor.h>
|
||||
|
||||
/* Convenience macro for exporting symbols */
|
||||
#ifndef flecs_dash_STATIC
|
||||
#if flecs_dash_EXPORTS && (defined(_MSC_VER) || defined(__MINGW32__))
|
||||
#define FLECS_DASH_API __declspec(dllexport)
|
||||
#elif flecs_dash_EXPORTS
|
||||
#define FLECS_DASH_API __attribute__((__visibility__("default")))
|
||||
#elif defined _MSC_VER
|
||||
#define FLECS_DASH_API __declspec(dllimport)
|
||||
#else
|
||||
#define FLECS_DASH_API
|
||||
#endif
|
||||
#else
|
||||
#define FLECS_DASH_API
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
#ifndef FLECS_DASH_MONITOR_H
|
||||
#define FLECS_DASH_MONITOR_H
|
||||
|
||||
/* This generated file contains includes for project dependencies */
|
||||
|
||||
ECS_STRUCT(EcsFps, {
|
||||
double value;
|
||||
double count;
|
||||
double target;
|
||||
});
|
||||
|
||||
ECS_STRUCT(EcsTick, {
|
||||
double count;
|
||||
});
|
||||
|
||||
ECS_STRUCT(EcsLoad, {
|
||||
double total_time_count;
|
||||
double frame_time_count;
|
||||
double system_time_count;
|
||||
double merge_time_count;
|
||||
});
|
||||
|
||||
ECS_STRUCT(EcsWorldClock, {
|
||||
double world_time;
|
||||
double world_time_raw;
|
||||
});
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef struct FlecsDashMonitor {
|
||||
ECS_DECLARE_COMPONENT(EcsFps);
|
||||
} FlecsDashMonitor;
|
||||
|
||||
FLECS_DASH_API
|
||||
void FlecsDashMonitorImport(
|
||||
ecs_world_t *world);
|
||||
|
||||
#define FlecsDashMonitorImportHandles(handles)\
|
||||
ECS_IMPORT_COMPONENT(handles, EcsFps);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
//// Module implementation
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
ECS_STRUCT(EcsDashServer, {
|
||||
int16_t port;
|
||||
});
|
||||
|
||||
ECS_STRUCT(EcsDashApp, {
|
||||
char *path;
|
||||
char *icon;
|
||||
});
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef struct FlecsDash {
|
||||
ECS_DECLARE_COMPONENT(EcsDashServer);
|
||||
ECS_DECLARE_COMPONENT(EcsDashApp);
|
||||
} FlecsDash;
|
||||
|
||||
FLECS_DASH_API
|
||||
void FlecsDashImport(
|
||||
ecs_world_t *world);
|
||||
|
||||
#define FlecsDashImportHandles(handles)\
|
||||
ECS_IMPORT_COMPONENT(handles, EcsDashServer);\
|
||||
ECS_IMPORT_COMPONENT(handles, EcsDashApp)
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
#ifndef FLECS_NO_CPP
|
||||
|
||||
namespace flecs {
|
||||
|
||||
class dash : public FlecsDash {
|
||||
public:
|
||||
using Server = EcsDashServer;
|
||||
using App = EcsDashApp;
|
||||
|
||||
dash(flecs::world& world) {
|
||||
FlecsDashImport(world.c_ptr());
|
||||
|
||||
flecs::module<flecs::dash>(world, "flecs::dash");
|
||||
|
||||
flecs::component<Server>(world, "flecs::dash::Server");
|
||||
flecs::component<App>(world, "flecs::dash::App");
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // FLECS_NO_CPP
|
||||
#endif // __cplusplus
|
||||
|
||||
#endif
|
|
@ -0,0 +1,561 @@
|
|||
#ifndef FLECS_JSON_IMPL
|
||||
#include "flecs_json.h"
|
||||
#endif
|
||||
|
||||
/* Simple serializer to turn values into strings. Use this code as a template
|
||||
* for when implementing a new serializer. */
|
||||
|
||||
static
|
||||
void json_ser_type(
|
||||
ecs_world_t *world,
|
||||
ecs_vector_t *ser,
|
||||
const void *base,
|
||||
ecs_strbuf_t *str);
|
||||
|
||||
static
|
||||
void json_ser_type_op(
|
||||
ecs_world_t *world,
|
||||
ecs_type_op_t *op,
|
||||
const void *base,
|
||||
ecs_strbuf_t *str);
|
||||
|
||||
/* Serialize a primitive value */
|
||||
static
|
||||
void json_ser_primitive(
|
||||
ecs_world_t *world,
|
||||
ecs_type_op_t *op,
|
||||
const void *base,
|
||||
ecs_strbuf_t *str)
|
||||
{
|
||||
const char *bool_str[] = { "false", "true" };
|
||||
|
||||
switch(op->is.primitive) {
|
||||
case EcsBool:
|
||||
ecs_strbuf_appendstr(str, bool_str[(int)*(bool*)base]);
|
||||
break;
|
||||
case EcsChar:
|
||||
ecs_strbuf_appendstrn(str, "\"", 1);
|
||||
ecs_strbuf_appendstrn(str, (char*)base, 1);
|
||||
ecs_strbuf_appendstrn(str, "\"", 1);
|
||||
break;
|
||||
case EcsByte:
|
||||
ecs_strbuf_append(str, "%u", *(uint8_t*)base);
|
||||
break;
|
||||
case EcsU8:
|
||||
ecs_strbuf_append(str, "%u", *(uint8_t*)base);
|
||||
break;
|
||||
case EcsU16:
|
||||
ecs_strbuf_append(str, "%u", *(uint16_t*)base);
|
||||
break;
|
||||
case EcsU32:
|
||||
ecs_strbuf_append(str, "%u", *(uint32_t*)base);
|
||||
break;
|
||||
case EcsU64:
|
||||
ecs_strbuf_append(str, "%u", *(uint64_t*)base);
|
||||
break;
|
||||
case EcsI8:
|
||||
ecs_strbuf_append(str, "%d", *(int8_t*)base);
|
||||
break;
|
||||
case EcsI16:
|
||||
ecs_strbuf_append(str, "%d", *(int16_t*)base);
|
||||
break;
|
||||
case EcsI32:
|
||||
ecs_strbuf_append(str, "%d", *(int32_t*)base);
|
||||
break;
|
||||
case EcsI64:
|
||||
ecs_strbuf_append(str, "%d", *(int64_t*)base);
|
||||
break;
|
||||
case EcsF32:
|
||||
ecs_strbuf_append(str, "%f", *(float*)base);
|
||||
break;
|
||||
case EcsF64:
|
||||
ecs_strbuf_append(str, "%f", *(double*)base);
|
||||
break;
|
||||
case EcsIPtr:
|
||||
ecs_strbuf_append(str, "%i", *(intptr_t*)base);
|
||||
break;
|
||||
case EcsUPtr:
|
||||
ecs_strbuf_append(str, "%u", *(uintptr_t*)base);
|
||||
break;
|
||||
case EcsString: {
|
||||
char *value = *(char**)base;
|
||||
if (value) {
|
||||
ecs_strbuf_appendstrn(str, "\"", 1);
|
||||
ecs_strbuf_appendstr(str, value);
|
||||
ecs_strbuf_appendstrn(str, "\"", 1);
|
||||
} else {
|
||||
ecs_strbuf_appendstr(str, "null");
|
||||
}
|
||||
break;
|
||||
}
|
||||
case EcsEntity:
|
||||
ecs_strbuf_appendstrn(str, "\"", 1);
|
||||
ecs_strbuf_appendstr(str, ecs_get_name(world, *(ecs_entity_t*)base));
|
||||
ecs_strbuf_appendstrn(str, "\"", 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* Serialize enumeration */
|
||||
static
|
||||
void json_ser_enum(
|
||||
ecs_world_t *world,
|
||||
ecs_type_op_t *op,
|
||||
const void *base,
|
||||
ecs_strbuf_t *str)
|
||||
{
|
||||
const EcsEnum *enum_type = ecs_get_ref_w_entity(world, &op->is.constant, 0, 0);
|
||||
ecs_assert(enum_type != NULL, ECS_INVALID_PARAMETER, NULL);
|
||||
|
||||
int32_t value = *(int32_t*)base;
|
||||
|
||||
/* Enumeration constants are stored in a map that is keyed on the
|
||||
* enumeration value. */
|
||||
char **constant = ecs_map_get(enum_type->constants, char*, value);
|
||||
ecs_assert(constant != NULL, ECS_INVALID_PARAMETER, NULL);
|
||||
|
||||
ecs_strbuf_appendstrn(str, "\"", 1);
|
||||
ecs_strbuf_appendstr(str, *constant);
|
||||
ecs_strbuf_appendstrn(str, "\"", 1);
|
||||
}
|
||||
|
||||
/* Serialize bitmask */
|
||||
static
|
||||
void json_ser_bitmask(
|
||||
ecs_world_t *world,
|
||||
ecs_type_op_t *op,
|
||||
const void *base,
|
||||
ecs_strbuf_t *str)
|
||||
{
|
||||
const EcsBitmask *bitmask_type = ecs_get_ref_w_entity(world, &op->is.constant, 0, 0);
|
||||
ecs_assert(bitmask_type != NULL, ECS_INVALID_PARAMETER, NULL);
|
||||
|
||||
int32_t value = *(int32_t*)base;
|
||||
ecs_map_key_t key;
|
||||
char **constant;
|
||||
|
||||
ecs_strbuf_list_push(str, "[", ",");
|
||||
|
||||
/* Multiple flags can be set at a given time. Iterate through all the flags
|
||||
* and append the ones that are set. */
|
||||
ecs_map_iter_t it = ecs_map_iter(bitmask_type->constants);
|
||||
while ((constant = ecs_map_next(&it, char*, &key))) {
|
||||
if ((value & key) == key) {
|
||||
ecs_strbuf_list_append(str, "\"%s\"", *constant);
|
||||
}
|
||||
}
|
||||
|
||||
ecs_strbuf_list_pop(str, "]");
|
||||
}
|
||||
|
||||
/* Serialize elements of a contiguous array */
|
||||
static
|
||||
void json_ser_elements(
|
||||
ecs_world_t *world,
|
||||
ecs_vector_t *elem_ops,
|
||||
const void *base,
|
||||
int32_t elem_count,
|
||||
int32_t elem_size,
|
||||
ecs_strbuf_t *str)
|
||||
{
|
||||
ecs_strbuf_list_push(str, "[", ",");
|
||||
|
||||
const void *ptr = base;
|
||||
|
||||
int i;
|
||||
for (i = 0; i < elem_count; i ++) {
|
||||
ecs_strbuf_list_next(str);
|
||||
json_ser_type(world, elem_ops, ptr, str);
|
||||
ptr = ECS_OFFSET(ptr, elem_size);
|
||||
}
|
||||
|
||||
ecs_strbuf_list_pop(str, "]");
|
||||
}
|
||||
|
||||
/* Serialize array */
|
||||
static
|
||||
void json_ser_array(
|
||||
ecs_world_t *world,
|
||||
ecs_type_op_t *op,
|
||||
void *base,
|
||||
ecs_strbuf_t *str)
|
||||
{
|
||||
const EcsMetaTypeSerializer *ser = ecs_get_ref_w_entity(world, &op->is.collection, 0, 0);
|
||||
ecs_assert(ser != NULL, ECS_INTERNAL_ERROR, NULL);
|
||||
|
||||
json_ser_elements(world, ser->ops, base, op->count, op->size, str);
|
||||
}
|
||||
|
||||
/* Serialize vector */
|
||||
static
|
||||
void json_ser_vector(
|
||||
ecs_world_t *world,
|
||||
ecs_type_op_t *op,
|
||||
const void *base,
|
||||
ecs_strbuf_t *str)
|
||||
{
|
||||
ecs_vector_t *value = *(ecs_vector_t**)base;
|
||||
|
||||
int32_t count = ecs_vector_count(value);
|
||||
void *array = ecs_vector_first_t(value, op->size, op->alignment);
|
||||
|
||||
const EcsMetaTypeSerializer *ser = ecs_get_ref_w_entity(world, &op->is.collection, 0, 0);
|
||||
ecs_assert(ser != NULL, ECS_INTERNAL_ERROR, NULL);
|
||||
|
||||
ecs_vector_t *elem_ops = ser->ops;
|
||||
|
||||
ecs_type_op_t *elem_op_hdr = (ecs_type_op_t*)ecs_vector_first(elem_ops, ecs_type_op_t);
|
||||
ecs_assert(elem_op_hdr != NULL, ECS_INTERNAL_ERROR, NULL);
|
||||
ecs_assert(elem_op_hdr->kind == EcsOpHeader, ECS_INTERNAL_ERROR, NULL);
|
||||
size_t elem_size = elem_op_hdr->size;
|
||||
|
||||
/* Serialize contiguous buffer of vector */
|
||||
json_ser_elements(world, elem_ops, array, count, elem_size, str);
|
||||
}
|
||||
|
||||
/* Serialize map */
|
||||
static
|
||||
void json_ser_map(
|
||||
ecs_world_t *world,
|
||||
ecs_type_op_t *op,
|
||||
const void *base,
|
||||
ecs_strbuf_t *str)
|
||||
{
|
||||
ecs_map_t *value = *(ecs_map_t**)base;
|
||||
|
||||
const EcsMetaTypeSerializer *key_ser = ecs_get_ref_w_entity(world, &op->is.map.key, 0, 0);
|
||||
ecs_assert(key_ser != NULL, ECS_INTERNAL_ERROR, NULL);
|
||||
|
||||
const EcsMetaTypeSerializer *elem_ser = ecs_get_ref_w_entity(world, &op->is.map.element, 0, 0);
|
||||
ecs_assert(elem_ser != NULL, ECS_INTERNAL_ERROR, NULL);
|
||||
|
||||
/* 2 instructions, one for the header */
|
||||
ecs_assert(ecs_vector_count(key_ser->ops) == 2, ECS_INTERNAL_ERROR, NULL);
|
||||
|
||||
ecs_type_op_t *key_op = ecs_vector_first(key_ser->ops, ecs_type_op_t);
|
||||
ecs_assert(key_op->kind == EcsOpHeader, ECS_INTERNAL_ERROR, NULL);
|
||||
key_op = &key_op[1];
|
||||
|
||||
ecs_map_iter_t it = ecs_map_iter(value);
|
||||
ecs_map_key_t key;
|
||||
void *ptr;
|
||||
|
||||
ecs_strbuf_list_push(str, "{", ",");
|
||||
|
||||
while ((ptr = _ecs_map_next(&it, 0, &key))) {
|
||||
ecs_strbuf_list_next(str);
|
||||
ecs_strbuf_appendstrn(str, "\"", 1);
|
||||
json_ser_type_op(world, key_op, (void*)&key, str);
|
||||
ecs_strbuf_appendstrn(str, "\"", 1);
|
||||
ecs_strbuf_appendstr(str, ":");
|
||||
json_ser_type(world, elem_ser->ops, ptr, str);
|
||||
|
||||
key = 0;
|
||||
}
|
||||
|
||||
ecs_strbuf_list_pop(str, "}");
|
||||
}
|
||||
|
||||
/* Forward serialization to the different type kinds */
|
||||
static
|
||||
void json_ser_type_op(
|
||||
ecs_world_t *world,
|
||||
ecs_type_op_t *op,
|
||||
const void *base,
|
||||
ecs_strbuf_t *str)
|
||||
{
|
||||
switch(op->kind) {
|
||||
case EcsOpHeader:
|
||||
case EcsOpPush:
|
||||
case EcsOpPop:
|
||||
/* Should not be parsed as single op */
|
||||
ecs_abort(ECS_INVALID_PARAMETER, NULL);
|
||||
break;
|
||||
case EcsOpPrimitive:
|
||||
json_ser_primitive(world, op, ECS_OFFSET(base, op->offset), str);
|
||||
break;
|
||||
case EcsOpEnum:
|
||||
json_ser_enum(world, op, ECS_OFFSET(base, op->offset), str);
|
||||
break;
|
||||
case EcsOpBitmask:
|
||||
json_ser_bitmask(world, op, ECS_OFFSET(base, op->offset), str);
|
||||
break;
|
||||
case EcsOpArray:
|
||||
json_ser_array(world, op, ECS_OFFSET(base, op->offset), str);
|
||||
break;
|
||||
case EcsOpVector:
|
||||
json_ser_vector(world, op, ECS_OFFSET(base, op->offset), str);
|
||||
break;
|
||||
case EcsOpMap:
|
||||
json_ser_map(world, op, ECS_OFFSET(base, op->offset), str);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* Iterate over the type ops of a type */
|
||||
static
|
||||
void json_ser_type(
|
||||
ecs_world_t *world,
|
||||
ecs_vector_t *ser,
|
||||
const void *base,
|
||||
ecs_strbuf_t *str)
|
||||
{
|
||||
ecs_type_op_t *ops = (ecs_type_op_t*)ecs_vector_first(ser, ecs_type_op_t);
|
||||
int32_t count = ecs_vector_count(ser);
|
||||
|
||||
for (int i = 0; i < count; i ++) {
|
||||
ecs_type_op_t *op = &ops[i];
|
||||
|
||||
if (op->name) {
|
||||
if (op->kind != EcsOpHeader) {
|
||||
ecs_strbuf_list_next(str);
|
||||
}
|
||||
|
||||
ecs_strbuf_append(str, "\"%s\":", op->name);
|
||||
}
|
||||
|
||||
switch(op->kind) {
|
||||
case EcsOpHeader:
|
||||
break;
|
||||
case EcsOpPush:
|
||||
ecs_strbuf_list_push(str, "{", ",");
|
||||
break;
|
||||
case EcsOpPop:
|
||||
ecs_strbuf_list_pop(str, "}");
|
||||
break;
|
||||
default:
|
||||
json_ser_type_op(world, op, base, str);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
char* ecs_ptr_to_json(
|
||||
ecs_world_t *world,
|
||||
ecs_entity_t type,
|
||||
void* ptr)
|
||||
{
|
||||
ecs_entity_t ecs_typeid(EcsMetaTypeSerializer) = ecs_lookup(world, "EcsMetaTypeSerializer");
|
||||
const EcsMetaTypeSerializer *ser = ecs_get(world, type, EcsMetaTypeSerializer);
|
||||
ecs_assert(ser != NULL, ECS_INVALID_PARAMETER, NULL);
|
||||
|
||||
ecs_strbuf_t str = ECS_STRBUF_INIT;
|
||||
json_ser_type(world, ser->ops, ptr, &str);
|
||||
return ecs_strbuf_get(&str);
|
||||
}
|
||||
|
||||
void json_ser_column(
|
||||
ecs_world_t *world,
|
||||
const EcsMetaTypeSerializer *ser,
|
||||
void *ptr,
|
||||
int32_t count,
|
||||
ecs_strbuf_t *str)
|
||||
{
|
||||
ecs_vector_t *ops = ser->ops;
|
||||
ecs_type_op_t *hdr = ecs_vector_first(ops, ecs_type_op_t);
|
||||
ecs_assert(hdr->kind == EcsOpHeader, ECS_INTERNAL_ERROR, NULL);
|
||||
int32_t size = hdr->size;
|
||||
|
||||
ecs_strbuf_list_push(str, "[", ",");
|
||||
|
||||
int i;
|
||||
for (i = 0; i < count; i ++) {
|
||||
ecs_strbuf_list_next(str);
|
||||
json_ser_type(world, ops, ptr, str);
|
||||
ptr = ECS_OFFSET(ptr, size);
|
||||
}
|
||||
|
||||
ecs_strbuf_list_pop(str, "]");
|
||||
}
|
||||
|
||||
static
|
||||
void serialize_type(
|
||||
ecs_world_t *world,
|
||||
ecs_type_t type,
|
||||
ecs_strbuf_t *str)
|
||||
{
|
||||
ecs_strbuf_list_push(str, "[", ",");
|
||||
|
||||
int i, count = ecs_vector_count(type);
|
||||
ecs_entity_t *comps = ecs_vector_first(type, ecs_entity_t);
|
||||
for (i = 0; i < count; i ++) {
|
||||
ecs_entity_t comp = comps[i];
|
||||
bool has_role = (comp & ECS_COMPONENT_MASK) != comp;
|
||||
ecs_entity_t comp_e = comp & ECS_COMPONENT_MASK;
|
||||
|
||||
if (has_role) {
|
||||
ecs_strbuf_list_next(str);
|
||||
ecs_strbuf_list_push(str, "[", ",");
|
||||
ecs_strbuf_list_append(str, "\"%s\"", ecs_role_str(comp));
|
||||
}
|
||||
|
||||
if (ECS_HAS_ROLE(comp, TRAIT)) {
|
||||
ecs_entity_t hi = ecs_entity_t_hi(comp_e);
|
||||
char *hi_path = ecs_get_fullpath(world, hi);
|
||||
ecs_strbuf_list_append(str, "\"%s\"", hi_path);
|
||||
ecs_os_free(hi_path);
|
||||
comp_e = ecs_entity_t_lo(comp_e);
|
||||
}
|
||||
|
||||
char *comp_path = ecs_get_fullpath(world, comp_e);
|
||||
if (comp_path) {
|
||||
ecs_strbuf_list_append(str, "\"%s\"", comp_path);
|
||||
} else {
|
||||
ecs_strbuf_list_append(str, "%d", (int32_t)comp);
|
||||
}
|
||||
|
||||
if (has_role) {
|
||||
ecs_strbuf_list_pop(str, "]");
|
||||
}
|
||||
|
||||
ecs_os_free(comp_path);
|
||||
}
|
||||
|
||||
ecs_strbuf_list_pop(str, "]");
|
||||
}
|
||||
|
||||
char* ecs_type_to_json(
|
||||
ecs_world_t *world,
|
||||
ecs_type_t type)
|
||||
{
|
||||
ecs_strbuf_t str = ECS_STRBUF_INIT;
|
||||
serialize_type(world, type, &str);
|
||||
return ecs_strbuf_get(&str);
|
||||
}
|
||||
|
||||
char* ecs_iter_to_json(
|
||||
ecs_world_t *world,
|
||||
ecs_iter_t *it,
|
||||
ecs_iter_next_action_t iter_next,
|
||||
ecs_type_t select)
|
||||
{
|
||||
ecs_strbuf_t str = ECS_STRBUF_INIT;
|
||||
ecs_entity_t ecs_typeid(EcsMetaTypeSerializer) =
|
||||
ecs_lookup_fullpath(world, "flecs.meta.MetaTypeSerializer");
|
||||
ecs_assert(ecs_typeid(EcsMetaTypeSerializer) != 0, ECS_INTERNAL_ERROR, NULL);
|
||||
|
||||
ecs_strbuf_list_push(&str, "[", ",");
|
||||
|
||||
while (iter_next(it)) {
|
||||
ecs_type_t table_type = ecs_iter_type(it);
|
||||
ecs_entity_t *comps = ecs_vector_first(table_type, ecs_entity_t);
|
||||
int32_t i, count = ecs_vector_count(table_type);
|
||||
|
||||
if (!it->count) {
|
||||
continue;
|
||||
}
|
||||
|
||||
ecs_strbuf_list_next(&str);
|
||||
ecs_strbuf_list_push(&str, "{", ",");
|
||||
|
||||
/* Serialize type */
|
||||
ecs_strbuf_list_appendstr(&str, "\"type\":");
|
||||
serialize_type(world, table_type, &str);
|
||||
|
||||
/* Add entity identifiers */
|
||||
ecs_strbuf_list_appendstr(&str, "\"entities\":");
|
||||
ecs_strbuf_list_push(&str, "[", ",");
|
||||
for (i = 0; i < it->count; i ++) {
|
||||
ecs_strbuf_list_append(&str, "%d", (int32_t)it->entities[i]);
|
||||
}
|
||||
ecs_strbuf_list_pop(&str, "]");
|
||||
|
||||
/* Serialize data */
|
||||
ecs_strbuf_list_appendstr(&str, "\"data\":");
|
||||
ecs_strbuf_list_push(&str, "{", ",");
|
||||
|
||||
for (i = 0; i < count; i ++) {
|
||||
if (select) {
|
||||
if (!ecs_type_has_entity(world, select, comps[i])) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
const EcsMetaTypeSerializer *ser = ecs_get(
|
||||
world, comps[i], EcsMetaTypeSerializer);
|
||||
|
||||
/* Don't serialize if there's no metadata for component */
|
||||
if (!ser) {
|
||||
continue;
|
||||
}
|
||||
|
||||
char *comp_path = ecs_get_fullpath(world, comps[i]);
|
||||
ecs_strbuf_list_append(&str, "\"%s\":", comp_path);
|
||||
ecs_os_free(comp_path);
|
||||
|
||||
json_ser_column(
|
||||
world, ser, ecs_table_column(it, i), it->count, &str);
|
||||
}
|
||||
|
||||
ecs_strbuf_list_pop(&str, "}");
|
||||
ecs_strbuf_list_pop(&str, "}");
|
||||
}
|
||||
|
||||
ecs_strbuf_list_pop(&str, "]");
|
||||
|
||||
return ecs_strbuf_get(&str);
|
||||
}
|
||||
|
||||
char* ecs_entity_to_json(
|
||||
ecs_world_t *world,
|
||||
ecs_entity_t entity,
|
||||
ecs_type_t select)
|
||||
{
|
||||
ecs_strbuf_t str = ECS_STRBUF_INIT;
|
||||
ecs_entity_t ecs_typeid(EcsMetaTypeSerializer) =
|
||||
ecs_lookup_fullpath(world, "flecs.meta.MetaTypeSerializer");
|
||||
ecs_assert(ecs_typeid(EcsMetaTypeSerializer) != 0, ECS_INTERNAL_ERROR, NULL);
|
||||
|
||||
ecs_strbuf_list_push(&str, "{", ",");
|
||||
|
||||
/* Serialize type */
|
||||
ecs_type_t type = ecs_get_type(world, entity);
|
||||
ecs_strbuf_list_appendstr(&str, "\"type\":");
|
||||
serialize_type(world, type, &str);
|
||||
|
||||
/* Serialize entity id */
|
||||
ecs_strbuf_list_append(&str, "\"entity\":%d", (int32_t)entity);
|
||||
|
||||
/* Serialize entity path */
|
||||
char *path = ecs_get_fullpath(world, entity);
|
||||
ecs_strbuf_list_append(&str, "\"path\":\"%s\"", path);
|
||||
ecs_os_free(path);
|
||||
|
||||
/* Serialize data */
|
||||
if (type) {
|
||||
ecs_strbuf_list_appendstr(&str, "\"data\":");
|
||||
ecs_strbuf_list_push(&str, "{", ",");
|
||||
|
||||
int i, count = ecs_vector_count(type);
|
||||
ecs_entity_t *comps = ecs_vector_first(type, ecs_entity_t);
|
||||
for (i = 0; i < count; i ++) {
|
||||
if (select) {
|
||||
if (!ecs_type_has_entity(world, select, comps[i])) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
const EcsMetaTypeSerializer *ser = ecs_get(
|
||||
world, comps[i], EcsMetaTypeSerializer);
|
||||
|
||||
/* Don't serialize if there's no metadata for component */
|
||||
if (!ser) {
|
||||
continue;
|
||||
}
|
||||
|
||||
char *comp_path = ecs_get_fullpath(world, comps[i]);
|
||||
ecs_strbuf_list_append(&str, "\"%s\":", comp_path);
|
||||
ecs_os_free(comp_path);
|
||||
|
||||
const void *comp_ptr = ecs_get_w_entity(world, entity, comps[i]);
|
||||
ecs_assert(comp_ptr != NULL, ECS_INTERNAL_ERROR, NULL);
|
||||
|
||||
json_ser_type(world, ser->ops, comp_ptr, &str);
|
||||
}
|
||||
|
||||
ecs_strbuf_list_pop(&str, "}");
|
||||
}
|
||||
|
||||
ecs_strbuf_list_pop(&str, "}");
|
||||
|
||||
return ecs_strbuf_get(&str);
|
||||
}
|
|
@ -0,0 +1,116 @@
|
|||
#define flecs_json_STATIC
|
||||
#ifndef FLECS_JSON_H
|
||||
#define FLECS_JSON_H
|
||||
|
||||
/* This generated file contains includes for project dependencies */
|
||||
/*
|
||||
)
|
||||
(.)
|
||||
.|.
|
||||
| |
|
||||
_.--| |--._
|
||||
.-'; ;`-'& ; `&.
|
||||
\ & ; & &_/
|
||||
|"""---...---"""|
|
||||
\ | | | | | | | /
|
||||
`---.|.|.|.---'
|
||||
|
||||
* This file is generated by bake.lang.c for your convenience. Headers of
|
||||
* dependencies will automatically show up in this file. Include bake_config.h
|
||||
* in your main project file. Do not edit! */
|
||||
|
||||
#ifndef FLECS_JSON_BAKE_CONFIG_H
|
||||
#define FLECS_JSON_BAKE_CONFIG_H
|
||||
|
||||
/* Headers of public dependencies */
|
||||
#include <flecs.h>
|
||||
#include <flecs_meta.h>
|
||||
|
||||
/* Convenience macro for exporting symbols */
|
||||
#ifndef flecs_json_STATIC
|
||||
#if flecs_json_EXPORTS && (defined(_MSC_VER) || defined(__MINGW32__))
|
||||
#define FLECS_JSON_API __declspec(dllexport)
|
||||
#elif flecs_json_EXPORTS
|
||||
#define FLECS_JSON_API __attribute__((__visibility__("default")))
|
||||
#elif defined _MSC_VER
|
||||
#define FLECS_JSON_API __declspec(dllimport)
|
||||
#else
|
||||
#define FLECS_JSON_API
|
||||
#endif
|
||||
#else
|
||||
#define FLECS_JSON_API
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
FLECS_JSON_API
|
||||
char* ecs_ptr_to_json(
|
||||
ecs_world_t *world,
|
||||
ecs_entity_t type,
|
||||
void *ptr);
|
||||
|
||||
FLECS_JSON_API
|
||||
char* ecs_entity_to_json(
|
||||
ecs_world_t *world,
|
||||
ecs_entity_t e,
|
||||
ecs_type_t select);
|
||||
|
||||
FLECS_JSON_API
|
||||
char* ecs_iter_to_json(
|
||||
ecs_world_t *world,
|
||||
ecs_iter_t *it,
|
||||
ecs_iter_next_action_t iter_next,
|
||||
ecs_type_t select);
|
||||
|
||||
FLECS_JSON_API
|
||||
char* ecs_type_to_json(
|
||||
ecs_world_t *world,
|
||||
ecs_type_t type);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
|
||||
namespace flecs {
|
||||
|
||||
template <typename T>
|
||||
std::string to_json(flecs::world& world, flecs::entity_t type, T& data) {
|
||||
char *str = ecs_ptr_to_json(world.c_ptr(), type, &data);
|
||||
std::string result = std::string(str);
|
||||
free(str);
|
||||
return result;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
std::string to_json(flecs::world& world, flecs::entity type, T& data) {
|
||||
return to_json(world, type.id(), data);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
std::string to_json(flecs::world& world, T& data) {
|
||||
entity_t type = _::component_info<T>::s_entity;
|
||||
return flecs::to_json(world, type, data);
|
||||
}
|
||||
|
||||
template <>
|
||||
inline std::string to_json<flecs::entity>(flecs::world& world, flecs::entity& entity) {
|
||||
char *str = ecs_entity_to_json(world.c_ptr(), entity.id(), nullptr);
|
||||
std::string result = std::string(str);
|
||||
free(str);
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,479 @@
|
|||
#ifndef FLECS_MONITOR_IMPL
|
||||
#include "flecs_monitor.h"
|
||||
#endif
|
||||
|
||||
typedef struct WorldStats {
|
||||
ecs_world_stats_t stats;
|
||||
} WorldStats;
|
||||
|
||||
typedef struct DeltaTime {
|
||||
ecs_gauge_t frame;
|
||||
ecs_gauge_t minute;
|
||||
int32_t t_frame;
|
||||
int32_t t_minute;
|
||||
} DeltaTime;
|
||||
|
||||
typedef struct PipelineStats {
|
||||
ecs_pipeline_stats_t stats;
|
||||
} PipelineStats;
|
||||
|
||||
static ECS_COMPONENT_DECLARE(WorldStats);
|
||||
static ECS_COMPONENT_DECLARE(DeltaTime);
|
||||
static ECS_COMPONENT_DECLARE(PipelineStats);
|
||||
|
||||
static void add_float_array(
|
||||
ecs_strbuf_t *r,
|
||||
int32_t t_start,
|
||||
const float *values,
|
||||
float scale)
|
||||
{
|
||||
ecs_strbuf_list_push(r, "[", ",");
|
||||
|
||||
int32_t i;
|
||||
for (i = 0; i < ECS_STAT_WINDOW; i ++) {
|
||||
int32_t t = (t_start + i + 1) % ECS_STAT_WINDOW;
|
||||
ecs_strbuf_list_append(r, "%f", values[t] / scale);
|
||||
}
|
||||
|
||||
ecs_strbuf_list_pop(r, "]");
|
||||
}
|
||||
|
||||
static void add_gauge(
|
||||
ecs_strbuf_t *r,
|
||||
const char *name,
|
||||
int32_t t,
|
||||
const ecs_gauge_t *m,
|
||||
bool min_max,
|
||||
float scale)
|
||||
{
|
||||
ecs_strbuf_list_append(r, "\"%s\":", name);
|
||||
ecs_strbuf_list_push(r, "{", ",");
|
||||
ecs_strbuf_list_append(r, "\"avg\":");
|
||||
add_float_array(r, t, m->avg, scale);
|
||||
|
||||
if (min_max) {
|
||||
ecs_strbuf_list_append(r, "\"min\":");
|
||||
add_float_array(r, t, m->min, scale);
|
||||
ecs_strbuf_list_append(r, "\"max\":");
|
||||
add_float_array(r, t, m->max, scale);
|
||||
}
|
||||
|
||||
ecs_strbuf_list_pop(r, "}");
|
||||
}
|
||||
|
||||
static void add_counter(
|
||||
ecs_strbuf_t *r,
|
||||
const char *name,
|
||||
int32_t t,
|
||||
const ecs_counter_t *m,
|
||||
bool min_max,
|
||||
float scale)
|
||||
{
|
||||
add_gauge(r, name, t, &m->rate, min_max, scale);
|
||||
}
|
||||
|
||||
#define add_current(r, name, t, m, scale)\
|
||||
_add_current(r, name, t, (ecs_gauge_t*)m, scale)
|
||||
|
||||
static void _add_current(
|
||||
ecs_strbuf_t *r,
|
||||
const char *name,
|
||||
int32_t t,
|
||||
const ecs_gauge_t *m,
|
||||
float scale)
|
||||
{
|
||||
ecs_strbuf_list_append(r, "\"%s\":%f", name, m->avg[t] / scale);
|
||||
}
|
||||
|
||||
static void add_world_stats(
|
||||
ecs_world_t *world,
|
||||
const ecs_world_info_t *info,
|
||||
ecs_strbuf_t *r)
|
||||
{
|
||||
const WorldStats *s = ecs_get(world, ecs_typeid(WorldStats), WorldStats);
|
||||
if (!s) {
|
||||
return;
|
||||
}
|
||||
|
||||
const DeltaTime *s_dt = ecs_get(world, ecs_typeid(DeltaTime), DeltaTime);
|
||||
if (!s_dt) {
|
||||
return;
|
||||
}
|
||||
|
||||
const ecs_world_stats_t *stats = &s->stats;
|
||||
int32_t t = stats->t;
|
||||
|
||||
float df = stats->frame_count_total.rate.avg[t];
|
||||
if (df == 0.0) {
|
||||
return;
|
||||
}
|
||||
|
||||
ecs_strbuf_list_appendstr(r, "\"world\":");
|
||||
ecs_strbuf_list_push(r, "{", ",");
|
||||
|
||||
ecs_strbuf_list_appendstr(r, "\"current\":");
|
||||
ecs_strbuf_list_push(r, "{", ",");
|
||||
add_current(r, "entity_count", t, &stats->entity_count, 1.0);
|
||||
add_current(r, "component_count", t, &stats->component_count, 1.0);
|
||||
add_current(r, "query_count", t, &stats->query_count, 1.0);
|
||||
add_current(r, "system_count", t, &stats->system_count, 1.0);
|
||||
add_current(r, "table_count", t, &stats->table_count, 1.0);
|
||||
add_current(r, "empty_table_count", t, &stats->empty_table_count, 1.0);
|
||||
add_current(r, "singleton_table_count", t, &stats->singleton_table_count, 1.0);
|
||||
add_current(r, "matched_table_count", t, &stats->matched_table_count, 1.0);
|
||||
add_current(r, "matched_entity_count", t, &stats->matched_entity_count, 1.0);
|
||||
add_current(r, "systems_ran", t, &stats->systems_ran_frame, df);
|
||||
add_current(r, "new_count", t, &stats->new_count, 1.0);
|
||||
add_current(r, "bulk_new_count", t, &stats->bulk_new_count, 1.0);
|
||||
add_current(r, "delete_count", t, &stats->delete_count, 1.0);
|
||||
add_current(r, "clear_count", t, &stats->clear_count, 1.0);
|
||||
add_current(r, "add_count", t, &stats->add_count, 1.0);
|
||||
add_current(r, "remove_count", t, &stats->remove_count, 1.0);
|
||||
add_current(r, "set_count", t, &stats->set_count, 1.0);
|
||||
add_current(r, "discard_count", t, &stats->discard_count, 1.0);
|
||||
ecs_strbuf_list_pop(r, "}");
|
||||
|
||||
ecs_strbuf_list_appendstr(r, "\"history_1m\":");
|
||||
ecs_strbuf_list_push(r, "{", ",");
|
||||
add_gauge(r, "fps", t, &stats->fps, false, 1.0);
|
||||
add_gauge(r, "delta_time", t, &s_dt->minute, true, 1.0);
|
||||
add_counter(r, "frame_time_total", t, &stats->frame_time_total, false, df);
|
||||
add_counter(r, "system_time_total", t, &stats->system_time_total, false, df);
|
||||
add_counter(r, "merge_time_total", t, &stats->merge_time_total, false, df);
|
||||
add_gauge(r, "entity_count", t, &stats->entity_count, false, 1.0);
|
||||
add_gauge(r, "matched_entity_count", t, &stats->matched_entity_count, false, 1.0);
|
||||
add_gauge(r, "table_count", t, &stats->table_count, false, 1.0);
|
||||
add_gauge(r, "empty_table_count", t, &stats->empty_table_count, false, 1.0);
|
||||
add_gauge(r, "singleton_table_count", t, &stats->singleton_table_count, false, 1.0);
|
||||
add_gauge(r, "matched_table_count", t, &stats->matched_table_count, false, 1.0);
|
||||
add_counter(r, "new_count", t, &stats->new_count, false, 1.0);
|
||||
add_counter(r, "bulk_new_count", t, &stats->bulk_new_count, false, 1.0);
|
||||
add_counter(r, "delete_count", t, &stats->delete_count, false, 1.0);
|
||||
add_counter(r, "clear_count", t, &stats->clear_count, false, 1.0);
|
||||
add_counter(r, "add_count", t, &stats->add_count, false, 1.0);
|
||||
add_counter(r, "remove_count", t, &stats->remove_count, false, 1.0);
|
||||
add_counter(r, "set_count", t, &stats->set_count, false, 1.0);
|
||||
add_counter(r, "discard_count", t, &stats->discard_count, false, 1.0);
|
||||
ecs_strbuf_list_pop(r, "}");
|
||||
|
||||
ecs_strbuf_list_pop(r, "}");
|
||||
}
|
||||
|
||||
static void add_signature(
|
||||
ecs_world_t *world,
|
||||
ecs_strbuf_t *r,
|
||||
ecs_entity_t system)
|
||||
{
|
||||
const EcsQuery *q = ecs_get(world, system, EcsQuery);
|
||||
if (q) {
|
||||
ecs_strbuf_list_append(r, "\"signature\":");
|
||||
ecs_strbuf_list_push(r, "[", ",");
|
||||
|
||||
ecs_sig_t *sig = ecs_query_get_sig(q->query);
|
||||
|
||||
ecs_sig_column_t *columns =
|
||||
ecs_vector_first(sig->columns, ecs_sig_column_t);
|
||||
int32_t i, count = ecs_vector_count(sig->columns);
|
||||
|
||||
for (i = 0; i < count; i ++) {
|
||||
ecs_sig_column_t *col = &columns[i];
|
||||
|
||||
if (col->oper_kind != EcsOperOr) {
|
||||
ecs_entity_t component = col->is.component;
|
||||
|
||||
ecs_strbuf_list_next(r);
|
||||
ecs_strbuf_list_push(r, "{", ",");
|
||||
|
||||
if (component & ECS_TRAIT) {
|
||||
ecs_entity_t
|
||||
lo = ecs_entity_t_lo(component & ECS_COMPONENT_MASK),
|
||||
hi = ecs_entity_t_hi(component & ECS_COMPONENT_MASK);
|
||||
|
||||
const char
|
||||
*lo_name = ecs_get_name(world, lo),
|
||||
*hi_name = ecs_get_name(world, hi);
|
||||
|
||||
if (!hi) {
|
||||
ecs_strbuf_list_append(r, "\"name\":\"TRAIT | %s\"", lo_name);
|
||||
} else {
|
||||
if (lo_name) {
|
||||
ecs_strbuf_list_append(r, "\"name\":\"%s FOR %s\"",
|
||||
hi_name, lo_name);
|
||||
} else {
|
||||
ecs_strbuf_list_append(r, "\"name\":\"%s FOR %u\"",
|
||||
hi_name, (uint32_t)lo);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ecs_strbuf_list_append(r, "\"name\":\"%s\"",
|
||||
ecs_get_name(world, col->is.component));
|
||||
}
|
||||
|
||||
ecs_entity_t actual = ecs_get_typeid(world, component);
|
||||
if (!ecs_has(world, actual, EcsComponent)) {
|
||||
ecs_strbuf_list_append(r, "\"tag\":true");
|
||||
}
|
||||
|
||||
if (col->inout_kind == EcsIn) {
|
||||
ecs_strbuf_list_append(r, "\"const\":true");
|
||||
}
|
||||
|
||||
if (col->oper_kind == EcsOperNot) {
|
||||
ecs_strbuf_list_append(r, "\"exclude\":true");
|
||||
}
|
||||
|
||||
if (col->from_kind != EcsFromOwned &&
|
||||
col->from_kind != EcsFromAny &&
|
||||
col->from_kind != EcsFromShared &&
|
||||
col->from_kind != EcsFromEmpty)
|
||||
{
|
||||
ecs_strbuf_list_append(r, "\"ref\":true");
|
||||
}
|
||||
|
||||
if (col->from_kind == EcsFromEmpty) {
|
||||
ecs_strbuf_list_append(r, "\"empty\":true");
|
||||
}
|
||||
|
||||
if (col->from_kind == EcsFromEntity && col->source == component) {
|
||||
ecs_strbuf_list_append(r, "\"singleton\":true");
|
||||
}
|
||||
|
||||
if (col->from_kind == EcsFromParent || col->from_kind == EcsCascade) {
|
||||
ecs_strbuf_list_append(r, "\"parent\":true");
|
||||
}
|
||||
|
||||
ecs_strbuf_list_pop(r, "}");
|
||||
}
|
||||
}
|
||||
|
||||
ecs_strbuf_list_pop(r, "]");
|
||||
}
|
||||
}
|
||||
|
||||
static void add_system(
|
||||
ecs_world_t *world,
|
||||
ecs_strbuf_t *r,
|
||||
const ecs_pipeline_stats_t *stats,
|
||||
ecs_entity_t system,
|
||||
float df)
|
||||
{
|
||||
ecs_system_stats_t *s = ecs_map_get(
|
||||
stats->system_stats, ecs_system_stats_t, system);
|
||||
|
||||
ecs_strbuf_list_next(r);
|
||||
ecs_strbuf_list_push(r, "{", ",");
|
||||
ecs_strbuf_list_append(r, "\"name\":\"%s\"", ecs_get_name(world, system));
|
||||
|
||||
ecs_entity_t module = ecs_get_parent_w_entity(world, system, EcsModule);
|
||||
if (module) {
|
||||
char *module_name = ecs_get_fullpath(world, module);
|
||||
ecs_strbuf_list_append(r, "\"module\":\"%s\"", module_name);
|
||||
ecs_os_free(module_name);
|
||||
}
|
||||
|
||||
add_signature(world, r, system);
|
||||
|
||||
if (s) {
|
||||
int32_t t = s->query_stats.t;
|
||||
ecs_strbuf_list_appendstr(r, "\"current\":");
|
||||
ecs_strbuf_list_push(r, "{", ",");
|
||||
add_current(r, "matched_table_count", t, &s->query_stats.matched_table_count, 1.0);
|
||||
add_current(r, "matched_empty_table_count", t, &s->query_stats.matched_empty_table_count, 1.0);
|
||||
add_current(r, "matched_entity_count", t, &s->query_stats.matched_entity_count, 1.0);
|
||||
add_current(r, "time_spent", t, &s->time_spent, df);
|
||||
add_current(r, "invoke_count", t, &s->invoke_count, df);
|
||||
add_current(r, "active", t, &s->active, 1.0);
|
||||
add_current(r, "enabled", t, &s->enabled, 1.0);
|
||||
ecs_strbuf_list_pop(r, "}");
|
||||
|
||||
ecs_strbuf_list_appendstr(r, "\"history_1m\":");
|
||||
ecs_strbuf_list_push(r, "{", ",");
|
||||
add_gauge(r, "matched_table_count", t, &s->query_stats.matched_table_count, false, 1.0);
|
||||
add_gauge(r, "matched_empty_table_count", t, &s->query_stats.matched_empty_table_count, false, 1.0);
|
||||
add_gauge(r, "matched_entity_count", t, &s->query_stats.matched_entity_count, false, 1.0);
|
||||
add_counter(r, "time_spent", t, &s->time_spent, false, 1.0);
|
||||
add_counter(r, "invoke_count", t, &s->invoke_count, false, 1.0);
|
||||
add_gauge(r, "active", t, &s->active, false, 1.0);
|
||||
add_gauge(r, "enabled", t, &s->enabled, false, 1.0);
|
||||
ecs_strbuf_list_pop(r, "}");
|
||||
}
|
||||
|
||||
ecs_strbuf_list_pop(r, "}");
|
||||
}
|
||||
|
||||
static void add_pipeline_stats(
|
||||
ecs_world_t *world,
|
||||
const ecs_world_info_t *info,
|
||||
ecs_strbuf_t *r)
|
||||
{
|
||||
/* Get number of frames passed in interval */
|
||||
const WorldStats *ws = ecs_get(world, ecs_typeid(WorldStats), WorldStats);
|
||||
if (!ws) {
|
||||
return;
|
||||
}
|
||||
|
||||
const ecs_world_stats_t *wstats = &ws->stats;
|
||||
int32_t t = wstats->t;
|
||||
float df = wstats->frame_count_total.rate.avg[t];
|
||||
if (df == 0.0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const PipelineStats *s = ecs_get(world,
|
||||
ecs_typeid(PipelineStats), PipelineStats);
|
||||
if (!s) {
|
||||
return;
|
||||
}
|
||||
|
||||
const ecs_pipeline_stats_t *stats = &s->stats;
|
||||
int32_t i, count = ecs_vector_count(stats->systems);
|
||||
ecs_entity_t *systems = ecs_vector_first(stats->systems, ecs_entity_t);
|
||||
|
||||
ecs_strbuf_list_appendstr(r, "\"pipeline\":");
|
||||
ecs_strbuf_list_push(r, "{", ",");
|
||||
|
||||
add_current(r, "fps", t, &wstats->fps, 1.0);
|
||||
add_current(r, "frame_time_total", t, &wstats->frame_time_total, df);
|
||||
add_current(r, "system_time_total", t, &wstats->system_time_total, df);
|
||||
add_current(r, "merge_time_total", t, &wstats->merge_time_total, df);
|
||||
|
||||
ecs_strbuf_list_appendstr(r, "\"systems\":");
|
||||
ecs_strbuf_list_push(r, "[", ",");
|
||||
for (i = 0; i < count; i ++) {
|
||||
if (systems[i]) {
|
||||
add_system(world, r, stats, systems[i], df);
|
||||
} else {
|
||||
/* Merge */
|
||||
ecs_strbuf_list_appendstr(r, "null");
|
||||
}
|
||||
}
|
||||
ecs_strbuf_list_pop(r, "]");
|
||||
|
||||
ecs_strbuf_list_pop(r, "}");
|
||||
}
|
||||
|
||||
static bool endpoint_world(
|
||||
ecs_world_t *world,
|
||||
ecs_entity_t entity,
|
||||
EcsHttpEndpoint *endpoint,
|
||||
EcsHttpRequest *request,
|
||||
EcsHttpReply *reply)
|
||||
{
|
||||
ecs_strbuf_t r = ECS_STRBUF_INIT;
|
||||
|
||||
const ecs_world_info_t *info = ecs_get_world_info(world);
|
||||
|
||||
ecs_strbuf_list_push(&r, "{", ",");
|
||||
|
||||
ecs_strbuf_list_append(&r, "\"target_fps\":%f", info->target_fps);
|
||||
|
||||
char param[256];
|
||||
if (ecs_http_get_query_param(
|
||||
request->params, "world", param, sizeof(param)) &&
|
||||
!strcmp(param, "yes"))
|
||||
{
|
||||
add_world_stats(world, info, &r);
|
||||
}
|
||||
|
||||
if (ecs_http_get_query_param(
|
||||
request->params, "pipeline", param, sizeof(param)) &&
|
||||
!strcmp(param, "yes"))
|
||||
{
|
||||
add_pipeline_stats(world, info, &r);
|
||||
}
|
||||
|
||||
ecs_strbuf_list_pop(&r, "}");
|
||||
|
||||
reply->body = ecs_strbuf_get(&r);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void CollectWorldStats(ecs_iter_t *it) {
|
||||
WorldStats *s = ecs_column(it, WorldStats, 1);
|
||||
ecs_get_world_stats(it->world, &s->stats);
|
||||
}
|
||||
|
||||
static void CollectPipelineStats(ecs_iter_t *it) {
|
||||
PipelineStats *s = ecs_column(it, PipelineStats, 1);
|
||||
ecs_get_pipeline_stats(it->world, ecs_get_pipeline(it->world), &s->stats);
|
||||
}
|
||||
|
||||
static void RecordDeltaTime(ecs_iter_t *it) {
|
||||
DeltaTime *s = ecs_column(it, DeltaTime, 1);
|
||||
int32_t t_frame = s->t_frame;
|
||||
s->frame.min[t_frame] = it->delta_time;
|
||||
s->frame.avg[t_frame] = it->delta_time;
|
||||
s->frame.max[t_frame] = it->delta_time;
|
||||
s->t_frame = (t_frame + 1) % ECS_STAT_WINDOW;
|
||||
}
|
||||
|
||||
static void CollectDeltaTime(ecs_iter_t *it) {
|
||||
DeltaTime *s = ecs_column(it, DeltaTime, 1);
|
||||
ecs_gauge_reduce(&s->minute, s->t_minute, &s->frame, s->t_frame);
|
||||
s->t_minute = (s->t_minute + 1) % ECS_STAT_WINDOW;
|
||||
}
|
||||
|
||||
static void RunServer(ecs_iter_t *it) {
|
||||
ecs_world_t *world = it->world;
|
||||
EcsMonitorServer *server = ecs_column(it, EcsMonitorServer, 1);
|
||||
ecs_entity_t ecs_typeid(EcsHttpServer) = ecs_column_entity(it, 2);
|
||||
ecs_entity_t ecs_typeid(EcsHttpEndpoint) = ecs_column_entity(it, 3);
|
||||
|
||||
int32_t i;
|
||||
for (i = 0; i < it->count; i ++) {
|
||||
ecs_entity_t e = it->entities[i];
|
||||
|
||||
ecs_set(world, e, EcsHttpServer, {.port = server[i].port});
|
||||
|
||||
/* Add endpoint to server that returns entity id of server */
|
||||
ecs_entity_t e_metrics = ecs_new_w_entity(world, ECS_CHILDOF | e);
|
||||
ecs_set(world, e_metrics, EcsName, {"e_metrics"});
|
||||
ecs_set(world, e_metrics, EcsHttpEndpoint, {
|
||||
.url = "metrics",
|
||||
.action = endpoint_world,
|
||||
.synchronous = true});
|
||||
}
|
||||
}
|
||||
|
||||
void FlecsMonitorImport(
|
||||
ecs_world_t *world)
|
||||
{
|
||||
ECS_MODULE(world, FlecsMonitor);
|
||||
|
||||
ECS_IMPORT(world, FlecsMeta);
|
||||
ECS_IMPORT(world, FlecsComponentsHttp);
|
||||
|
||||
ecs_set_name_prefix(world, "EcsMonitor");
|
||||
|
||||
ECS_META(world, EcsMonitorServer);
|
||||
|
||||
/* Private components */
|
||||
ECS_COMPONENT_DEFINE(world, WorldStats);
|
||||
ECS_COMPONENT_DEFINE(world, DeltaTime);
|
||||
ECS_COMPONENT_DEFINE(world, PipelineStats);
|
||||
|
||||
ECS_SYSTEM(world, RunServer, EcsOnSet, Server,
|
||||
:flecs.components.http.Server,
|
||||
:flecs.components.http.Endpoint);
|
||||
|
||||
ECS_SYSTEM(world, CollectWorldStats, EcsOnLoad, $WorldStats);
|
||||
ECS_SYSTEM(world, CollectPipelineStats, EcsOnLoad, $PipelineStats);
|
||||
ECS_SYSTEM(world, RecordDeltaTime, EcsOnLoad, $DeltaTime);
|
||||
ECS_SYSTEM(world, CollectDeltaTime, EcsOnLoad, $DeltaTime);
|
||||
|
||||
/* Collect statistics once per second */
|
||||
ecs_set_interval(world, CollectWorldStats, 1.0);
|
||||
ecs_set_interval(world, CollectPipelineStats, 1.0);
|
||||
ecs_set_interval(world, CollectDeltaTime, 1.0);
|
||||
|
||||
/* Initialize singleton entities for statistics components */
|
||||
ecs_set_ptr(world, ecs_typeid(WorldStats), WorldStats, NULL);
|
||||
ecs_set_ptr(world, ecs_typeid(PipelineStats), PipelineStats, NULL);
|
||||
ecs_set_ptr(world, ecs_typeid(DeltaTime), DeltaTime, NULL);
|
||||
|
||||
ECS_EXPORT_COMPONENT(EcsMonitorServer);
|
||||
|
||||
/* Enable system time monitoring */
|
||||
ecs_measure_system_time(world, true);
|
||||
}
|
|
@ -0,0 +1,70 @@
|
|||
#define flecs_monitor_STATIC
|
||||
#ifndef FLECS_MONITOR_H
|
||||
#define FLECS_MONITOR_H
|
||||
|
||||
/* This generated file contains includes for project dependencies */
|
||||
/*
|
||||
)
|
||||
(.)
|
||||
.|.
|
||||
| |
|
||||
_.--| |--._
|
||||
.-'; ;`-'& ; `&.
|
||||
\ & ; & &_/
|
||||
|"""---...---"""|
|
||||
\ | | | | | | | /
|
||||
`---.|.|.|.---'
|
||||
|
||||
* This file is generated by bake.lang.c for your convenience. Headers of
|
||||
* dependencies will automatically show up in this file. Include bake_config.h
|
||||
* in your main project file. Do not edit! */
|
||||
|
||||
#ifndef FLECS_MONITOR_BAKE_CONFIG_H
|
||||
#define FLECS_MONITOR_BAKE_CONFIG_H
|
||||
|
||||
/* Headers of public dependencies */
|
||||
#include <flecs.h>
|
||||
#include <flecs_meta.h>
|
||||
#include <flecs_components_http.h>
|
||||
|
||||
/* Convenience macro for exporting symbols */
|
||||
#ifndef flecs_monitor_STATIC
|
||||
#if flecs_monitor_EXPORTS && (defined(_MSC_VER) || defined(__MINGW32__))
|
||||
#define FLECS_MONITOR_API __declspec(dllexport)
|
||||
#elif flecs_monitor_EXPORTS
|
||||
#define FLECS_MONITOR_API __attribute__((__visibility__("default")))
|
||||
#elif defined _MSC_VER
|
||||
#define FLECS_MONITOR_API __declspec(dllimport)
|
||||
#else
|
||||
#define FLECS_MONITOR_API
|
||||
#endif
|
||||
#else
|
||||
#define FLECS_MONITOR_API
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
ECS_STRUCT(EcsMonitorServer, {
|
||||
int16_t port;
|
||||
});
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef struct FlecsMonitor {
|
||||
ECS_DECLARE_COMPONENT(EcsMonitorServer);
|
||||
} FlecsMonitor;
|
||||
|
||||
FLECS_MONITOR_API void FlecsMonitorImport(
|
||||
ecs_world_t *world);
|
||||
|
||||
#define FlecsMonitorImportHandles(handles) \
|
||||
ECS_IMPORT_COMPONENT(handles, EcsMonitorServer);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
|
@ -0,0 +1,152 @@
|
|||
#include "flecs_os_api_posix.h"
|
||||
|
||||
#include <pthread.h>
|
||||
|
||||
#include "flecs/flecs.h"
|
||||
|
||||
static
|
||||
ecs_os_thread_t posix_thread_new(
|
||||
ecs_os_thread_callback_t callback,
|
||||
void *arg)
|
||||
{
|
||||
pthread_t *thread = ecs_os_malloc(sizeof(pthread_t));
|
||||
int r;
|
||||
|
||||
if ((r = pthread_create (thread, NULL, callback, arg))) {
|
||||
abort();
|
||||
}
|
||||
|
||||
return (ecs_os_thread_t)(uintptr_t)thread;
|
||||
}
|
||||
|
||||
static
|
||||
void* posix_thread_join(
|
||||
ecs_os_thread_t thread)
|
||||
{
|
||||
void *arg;
|
||||
pthread_t *thr = (pthread_t*)(uintptr_t)thread;
|
||||
pthread_join(*thr, &arg);
|
||||
ecs_os_free(thr);
|
||||
return arg;
|
||||
}
|
||||
|
||||
static
|
||||
int32_t posix_ainc(int32_t *count) {
|
||||
int value;
|
||||
#ifdef __GNUC__
|
||||
value = __sync_add_and_fetch (count, 1);
|
||||
return value;
|
||||
#else
|
||||
/* Unsupported */
|
||||
abort();
|
||||
#endif
|
||||
}
|
||||
|
||||
static
|
||||
int32_t posix_adec(int32_t *count) {
|
||||
int value;
|
||||
#ifdef __GNUC__
|
||||
value = __sync_sub_and_fetch (count, 1);
|
||||
return value;
|
||||
#else
|
||||
/* Unsupported */
|
||||
abort();
|
||||
#endif
|
||||
}
|
||||
|
||||
static
|
||||
ecs_os_mutex_t posix_mutex_new(void) {
|
||||
pthread_mutex_t *mutex = ecs_os_malloc(sizeof(pthread_mutex_t));
|
||||
if (pthread_mutex_init(mutex, NULL)) {
|
||||
abort();
|
||||
}
|
||||
return (ecs_os_mutex_t)(uintptr_t)mutex;
|
||||
}
|
||||
|
||||
static
|
||||
void posix_mutex_free(ecs_os_mutex_t m) {
|
||||
pthread_mutex_t *mutex = (pthread_mutex_t*)(intptr_t)m;
|
||||
pthread_mutex_destroy(mutex);
|
||||
ecs_os_free(mutex);
|
||||
}
|
||||
|
||||
static
|
||||
void posix_mutex_lock(ecs_os_mutex_t m) {
|
||||
pthread_mutex_t *mutex = (pthread_mutex_t*)(intptr_t)m;
|
||||
if (pthread_mutex_lock(mutex)) {
|
||||
abort();
|
||||
}
|
||||
}
|
||||
|
||||
static
|
||||
void posix_mutex_unlock(ecs_os_mutex_t m) {
|
||||
pthread_mutex_t *mutex = (pthread_mutex_t*)(intptr_t)m;
|
||||
if (pthread_mutex_unlock(mutex)) {
|
||||
abort();
|
||||
}
|
||||
}
|
||||
|
||||
static
|
||||
ecs_os_cond_t posix_cond_new(void) {
|
||||
pthread_cond_t *cond = ecs_os_malloc(sizeof(pthread_cond_t));
|
||||
if (pthread_cond_init(cond, NULL)) {
|
||||
abort();
|
||||
}
|
||||
return (ecs_os_cond_t)(uintptr_t)cond;
|
||||
}
|
||||
|
||||
static
|
||||
void posix_cond_free(ecs_os_cond_t c) {
|
||||
pthread_cond_t *cond = (pthread_cond_t*)(intptr_t)c;
|
||||
if (pthread_cond_destroy(cond)) {
|
||||
abort();
|
||||
}
|
||||
ecs_os_free(cond);
|
||||
}
|
||||
|
||||
static
|
||||
void posix_cond_signal(ecs_os_cond_t c) {
|
||||
pthread_cond_t *cond = (pthread_cond_t*)(intptr_t)c;
|
||||
if (pthread_cond_signal(cond)) {
|
||||
abort();
|
||||
}
|
||||
}
|
||||
|
||||
static
|
||||
void posix_cond_broadcast(ecs_os_cond_t c) {
|
||||
pthread_cond_t *cond = (pthread_cond_t*)(intptr_t)c;
|
||||
if (pthread_cond_broadcast(cond)) {
|
||||
abort();
|
||||
}
|
||||
}
|
||||
|
||||
static
|
||||
void posix_cond_wait(ecs_os_cond_t c, ecs_os_mutex_t m) {
|
||||
pthread_cond_t *cond = (pthread_cond_t*)(intptr_t)c;
|
||||
pthread_mutex_t *mutex = (pthread_mutex_t*)(intptr_t)m;
|
||||
if (pthread_cond_wait(cond, mutex)) {
|
||||
abort();
|
||||
}
|
||||
}
|
||||
|
||||
void posix_set_os_api(void) {
|
||||
ecs_os_set_api_defaults();
|
||||
|
||||
ecs_os_api_t api = ecs_os_api;
|
||||
|
||||
api.thread_new_ = posix_thread_new;
|
||||
api.thread_join_ = posix_thread_join;
|
||||
api.ainc_ = posix_ainc;
|
||||
api.adec_ = posix_adec;
|
||||
api.mutex_new_ = posix_mutex_new;
|
||||
api.mutex_free_ = posix_mutex_free;
|
||||
api.mutex_lock_ = posix_mutex_lock;
|
||||
api.mutex_unlock_ = posix_mutex_unlock;
|
||||
api.cond_new_ = posix_cond_new;
|
||||
api.cond_free_ = posix_cond_free;
|
||||
api.cond_signal_ = posix_cond_signal;
|
||||
api.cond_broadcast_ = posix_cond_broadcast;
|
||||
api.cond_wait_ = posix_cond_wait;
|
||||
|
||||
ecs_os_set_api(&api);
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
#pragma once
|
||||
|
||||
void posix_set_os_api(void);
|
|
@ -0,0 +1,126 @@
|
|||
#ifndef FLECS_PLAYER_IMPL
|
||||
#include "flecs_player.h"
|
||||
#endif
|
||||
|
||||
static
|
||||
void player_start(
|
||||
ecs_world_t *world,
|
||||
void *ctx)
|
||||
{
|
||||
ecs_ref_t *ref = ctx;
|
||||
EcsPlayer *player = (EcsPlayer*)ecs_get_ref_w_entity(world, ref, 0, 0);
|
||||
ecs_assert(player != NULL, ECS_INTERNAL_ERROR, NULL);
|
||||
|
||||
player->snapshot = ecs_snapshot_take(world);
|
||||
player->time_scale = 1.0;
|
||||
|
||||
ecs_set_time_scale(world, 1.0);
|
||||
ecs_set_pipeline(world, player->play_pipeline);
|
||||
|
||||
ecs_os_free(ref);
|
||||
}
|
||||
|
||||
static
|
||||
void player_stop(
|
||||
ecs_world_t *world,
|
||||
void *ctx)
|
||||
{
|
||||
ecs_ref_t *ref = ctx;
|
||||
EcsPlayer *player = (EcsPlayer*)ecs_get_ref_w_entity(world, ref, 0, 0);
|
||||
ecs_assert(player != NULL, ECS_INTERNAL_ERROR, NULL);
|
||||
|
||||
if (player->snapshot) {
|
||||
ecs_snapshot_restore(world, player->snapshot);
|
||||
|
||||
player = (EcsPlayer*)ecs_get_ref_w_entity(world, ref, 0, 0);
|
||||
ecs_set_time_scale(world, 0);
|
||||
ecs_reset_clock(world);
|
||||
player->time_scale = 0;
|
||||
player->state = EcsPlayerStop;
|
||||
player->prev_state = EcsPlayerStop;
|
||||
}
|
||||
|
||||
player->snapshot = NULL;
|
||||
ecs_set_pipeline(world, player->stop_pipeline);
|
||||
|
||||
ecs_os_free(ref);
|
||||
}
|
||||
|
||||
static
|
||||
void EcsSetPlayer(ecs_iter_t *it) {
|
||||
ecs_world_t *world = it->world;
|
||||
|
||||
EcsPlayer *player = ecs_column(it, EcsPlayer, 1);
|
||||
ecs_entity_t ecs_typeid(EcsPlayer) = ecs_column_entity(it, 1);
|
||||
|
||||
int32_t i;
|
||||
for (i = 0; i < it->count; i ++) {
|
||||
EcsPlayerState prev_state = player[i].prev_state;
|
||||
EcsPlayerState state = player[i].state;
|
||||
|
||||
if (prev_state != state) {
|
||||
switch(player[i].state) {
|
||||
case EcsPlayerPause:
|
||||
player[i].time_scale = 0;
|
||||
break;
|
||||
case EcsPlayerPlay:
|
||||
if (prev_state == EcsPlayerStop) {
|
||||
ecs_ref_t *ref = ecs_os_calloc(sizeof(ecs_ref_t));
|
||||
ecs_get_ref(world, ref, it->entities[i], EcsPlayer);
|
||||
ecs_run_post_frame(it->world, player_start, ref);
|
||||
} else {
|
||||
player[i].time_scale = 1.0;
|
||||
}
|
||||
break;
|
||||
case EcsPlayerStop:
|
||||
if (prev_state != EcsPlayerStop) {
|
||||
ecs_ref_t *ref = ecs_os_calloc(sizeof(ecs_ref_t));
|
||||
ecs_get_ref(world, ref, it->entities[i], EcsPlayer);
|
||||
ecs_run_post_frame(world, player_stop, ref);
|
||||
}
|
||||
player[i].time_scale = 0;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
player[i].prev_state = state;
|
||||
|
||||
ecs_set_time_scale(it->world, player[i].time_scale);
|
||||
}
|
||||
}
|
||||
|
||||
void FlecsPlayerImport(
|
||||
ecs_world_t *world)
|
||||
{
|
||||
ECS_MODULE(world, FlecsPlayer);
|
||||
|
||||
ECS_IMPORT(world, FlecsMeta);
|
||||
|
||||
ecs_set_name_prefix(world, "Ecs");
|
||||
|
||||
ECS_META(world, EcsPlayerState);
|
||||
ECS_META(world, EcsPlayer);
|
||||
ECS_META(world, EcsTargetFps);
|
||||
|
||||
ECS_PIPELINE(world, StopPipeline,
|
||||
flecs.pipeline.PreFrame,
|
||||
flecs.pipeline.OnLoad,
|
||||
flecs.pipeline.PostLoad,
|
||||
flecs.pipeline.PreStore,
|
||||
flecs.pipeline.OnStore,
|
||||
flecs.pipeline.PostFrame);
|
||||
|
||||
ECS_SYSTEM(world, EcsSetPlayer, EcsOnSet, EcsPlayer);
|
||||
|
||||
ecs_set(world, EcsWorld, EcsPlayer, {
|
||||
.state = EcsPlayerPlay,
|
||||
.prev_state = EcsPlayerStop,
|
||||
.time_scale = 1,
|
||||
.play_pipeline = ecs_get_pipeline(world),
|
||||
.stop_pipeline = StopPipeline
|
||||
});
|
||||
|
||||
ECS_EXPORT_COMPONENT(EcsPlayer);
|
||||
}
|
|
@ -0,0 +1,88 @@
|
|||
#define flecs_player_STATIC
|
||||
#ifndef FLECS_PLAYER_H
|
||||
#define FLECS_PLAYER_H
|
||||
|
||||
/* This generated file contains includes for project dependencies */
|
||||
/*
|
||||
)
|
||||
(.)
|
||||
.|.
|
||||
| |
|
||||
_.--| |--._
|
||||
.-'; ;`-'& ; `&.
|
||||
\ & ; & &_/
|
||||
|"""---...---"""|
|
||||
\ | | | | | | | /
|
||||
`---.|.|.|.---'
|
||||
|
||||
* This file is generated by bake.lang.c for your convenience. Headers of
|
||||
* dependencies will automatically show up in this file. Include bake_config.h
|
||||
* in your main project file. Do not edit! */
|
||||
|
||||
#ifndef FLECS_PLAYER_BAKE_CONFIG_H
|
||||
#define FLECS_PLAYER_BAKE_CONFIG_H
|
||||
|
||||
/* Headers of public dependencies */
|
||||
#include <flecs.h>
|
||||
#include <flecs_meta.h>
|
||||
|
||||
/* Convenience macro for exporting symbols */
|
||||
#ifndef flecs_player_STATIC
|
||||
#if flecs_player_EXPORTS && (defined(_MSC_VER) || defined(__MINGW32__))
|
||||
#define FLECS_PLAYER_API __declspec(dllexport)
|
||||
#elif flecs_player_EXPORTS
|
||||
#define FLECS_PLAYER_API __attribute__((__visibility__("default")))
|
||||
#elif defined _MSC_VER
|
||||
#define FLECS_PLAYER_API __declspec(dllimport)
|
||||
#else
|
||||
#define FLECS_PLAYER_API
|
||||
#endif
|
||||
#else
|
||||
#define FLECS_PLAYER_API
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
ECS_ENUM(EcsPlayerState, {
|
||||
EcsPlayerPlay,
|
||||
EcsPlayerPause,
|
||||
EcsPlayerStop
|
||||
});
|
||||
|
||||
ECS_STRUCT(EcsPlayer, {
|
||||
EcsPlayerState state;
|
||||
EcsPlayerState prev_state;
|
||||
float time_scale;
|
||||
ecs_entity_t play_pipeline;
|
||||
ecs_entity_t stop_pipeline;
|
||||
|
||||
ECS_PRIVATE
|
||||
ecs_snapshot_t *snapshot;
|
||||
});
|
||||
|
||||
ECS_STRUCT(EcsTargetFps, {
|
||||
float target_fps;
|
||||
});
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef struct FlecsPlayer {
|
||||
ECS_DECLARE_COMPONENT(EcsPlayer);
|
||||
} FlecsPlayer;
|
||||
|
||||
FLECS_PLAYER_API
|
||||
void FlecsPlayerImport(
|
||||
ecs_world_t *world);
|
||||
|
||||
#define FlecsPlayerImportHandles(handles)\
|
||||
ECS_IMPORT_COMPONENT(handles, EcsPlayer);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
|
@ -0,0 +1,414 @@
|
|||
#ifndef FLECS_REST_IMPL
|
||||
#include "flecs_rest.h"
|
||||
#endif
|
||||
#include <stdio.h>
|
||||
#include <ctype.h>
|
||||
|
||||
static
|
||||
bool parse_filter(
|
||||
ecs_world_t *world,
|
||||
EcsHttpRequest *request,
|
||||
ecs_filter_t *filter)
|
||||
{
|
||||
char buffer[1024];
|
||||
|
||||
if (ecs_http_get_query_param(
|
||||
request->params, "include", buffer, sizeof(buffer)))
|
||||
{
|
||||
filter->include = ecs_type_from_str(world, buffer);
|
||||
filter->include_kind = EcsMatchAny;
|
||||
if (!filter->include) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (ecs_http_get_query_param(
|
||||
request->params, "exclude", buffer, sizeof(buffer)))
|
||||
{
|
||||
filter->exclude = ecs_type_from_str(world, buffer);
|
||||
if (!filter->exclude) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static
|
||||
bool parse_select(
|
||||
ecs_world_t *world,
|
||||
EcsHttpRequest *request,
|
||||
ecs_type_t *select)
|
||||
{
|
||||
char buffer[1024];
|
||||
|
||||
*select = NULL;
|
||||
|
||||
if (ecs_http_get_query_param(
|
||||
request->params, "select", buffer, sizeof(buffer)))
|
||||
{
|
||||
*select = ecs_type_from_str(world, buffer);
|
||||
if (!*select) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static
|
||||
bool parse_entity(
|
||||
ecs_world_t *world,
|
||||
EcsHttpRequest *request,
|
||||
ecs_entity_t *e_out)
|
||||
{
|
||||
ecs_entity_t e = 0;
|
||||
|
||||
if (request->relative_url && strlen(request->relative_url)) {
|
||||
/* Request scope of entity */
|
||||
const char *name = request->relative_url;
|
||||
|
||||
/* If name starts with a number, lookup by entity id */
|
||||
if (isdigit(name[0])) {
|
||||
e = atoi(name);
|
||||
} else {
|
||||
e = ecs_lookup_path_w_sep(world, 0, name, "/", NULL);
|
||||
if (!e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
*e_out = e;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static
|
||||
bool endpoint_filter(
|
||||
ecs_world_t *world,
|
||||
ecs_entity_t entity,
|
||||
EcsHttpEndpoint *endpoint,
|
||||
EcsHttpRequest *request,
|
||||
EcsHttpReply *reply)
|
||||
{
|
||||
ecs_filter_t filter = { };
|
||||
ecs_type_t select = NULL;
|
||||
|
||||
if (!parse_filter(world, request, &filter)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!parse_select(world, request, &select)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ecs_iter_t it = ecs_filter_iter(world, &filter);
|
||||
|
||||
reply->body = ecs_iter_to_json(world, &it, ecs_filter_next, select);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static
|
||||
bool endpoint_scope(
|
||||
ecs_world_t *world,
|
||||
ecs_entity_t entity,
|
||||
EcsHttpEndpoint *endpoint,
|
||||
EcsHttpRequest *request,
|
||||
EcsHttpReply *reply)
|
||||
{
|
||||
ecs_entity_t e = 0;
|
||||
ecs_filter_t filter = { };
|
||||
ecs_type_t select = NULL;
|
||||
|
||||
if (!parse_entity(world, request, &e)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!parse_filter(world, request, &filter)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!parse_select(world, request, &select)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ecs_iter_t it = ecs_scope_iter_w_filter(world, e, &filter);
|
||||
|
||||
reply->body = ecs_iter_to_json(world, &it, ecs_scope_next, select);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static
|
||||
bool endpoint_entity_new(
|
||||
ecs_world_t *world,
|
||||
EcsHttpReply *reply,
|
||||
const char *name,
|
||||
ecs_type_t select)
|
||||
{
|
||||
ecs_entity_t e = ecs_new_from_path(world, 0, name);
|
||||
if (select) {
|
||||
ecs_add_type(world, e, select);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static
|
||||
bool endpoint_entity_add(
|
||||
ecs_world_t *world,
|
||||
EcsHttpReply *reply,
|
||||
ecs_entity_t e,
|
||||
ecs_type_t select)
|
||||
{
|
||||
if (select) {
|
||||
ecs_add_type(world, e, select);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static
|
||||
bool endpoint_entity_remove(
|
||||
ecs_world_t *world,
|
||||
EcsHttpReply *reply,
|
||||
ecs_entity_t e,
|
||||
ecs_type_t select)
|
||||
{
|
||||
if (select) {
|
||||
ecs_remove_type(world, e, select);
|
||||
} else {
|
||||
ecs_delete(world, e);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static
|
||||
bool endpoint_entity_get(
|
||||
ecs_world_t *world,
|
||||
EcsHttpReply *reply,
|
||||
ecs_entity_t e,
|
||||
ecs_type_t select)
|
||||
{
|
||||
reply->body = ecs_entity_to_json(world, e, select);
|
||||
return true;
|
||||
}
|
||||
|
||||
static
|
||||
bool endpoint_entity(
|
||||
ecs_world_t *world,
|
||||
ecs_entity_t entity,
|
||||
EcsHttpEndpoint *endpoint,
|
||||
EcsHttpRequest *request,
|
||||
EcsHttpReply *reply)
|
||||
{
|
||||
ecs_entity_t e = 0;
|
||||
ecs_type_t select = NULL;
|
||||
|
||||
if (!parse_select(world, request, &select)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (request->method == EcsHttpPost) {
|
||||
return endpoint_entity_new(world, reply, request->relative_url, select);
|
||||
}
|
||||
|
||||
if (!parse_entity(world, request, &e)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (request->method == EcsHttpGet) {
|
||||
return endpoint_entity_get(world, reply, e, select);
|
||||
} else if (request->method == EcsHttpDelete) {
|
||||
return endpoint_entity_remove(world, reply, e, select);
|
||||
} else if (request->method == EcsHttpPut) {
|
||||
return endpoint_entity_add(world, reply, e, select);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static
|
||||
void serialize_browse_info(
|
||||
ecs_world_t *world,
|
||||
ecs_entity_t parent,
|
||||
ecs_entity_t e,
|
||||
ecs_strbuf_t *str)
|
||||
{
|
||||
ecs_strbuf_list_append(str, "\"id\":%u", (int32_t)e);
|
||||
|
||||
char *type = ecs_type_to_json(world, ecs_get_type(world, e));
|
||||
ecs_strbuf_list_append(str, "\"type\":%s", type);
|
||||
|
||||
const char *name = ecs_get_name(world, e);
|
||||
if (name) {
|
||||
ecs_strbuf_list_append(str, "\"name\":\"%s\"", name);
|
||||
|
||||
char *fullpath = ecs_get_fullpath(world, e);
|
||||
ecs_strbuf_list_append(str, "\"path\":\"%s\"", fullpath);
|
||||
}
|
||||
|
||||
int32_t child_count = ecs_get_child_count(world, e);
|
||||
ecs_strbuf_list_append(str, "\"child_count\":%u", child_count);
|
||||
}
|
||||
|
||||
static
|
||||
bool endpoint_browse(
|
||||
ecs_world_t *world,
|
||||
ecs_entity_t entity,
|
||||
EcsHttpEndpoint *endpoint,
|
||||
EcsHttpRequest *request,
|
||||
EcsHttpReply *reply)
|
||||
{
|
||||
ecs_filter_t filter = { };
|
||||
ecs_entity_t e;
|
||||
|
||||
if (!parse_entity(world, request, &e)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!parse_filter(world, request, &filter)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ecs_iter_t it = ecs_scope_iter_w_filter(world, e, &filter);
|
||||
ecs_strbuf_t str = ECS_STRBUF_INIT;
|
||||
|
||||
ecs_strbuf_list_push(&str, "[", ",");
|
||||
while (ecs_scope_next(&it)) {
|
||||
int32_t i;
|
||||
for (i = 0; i < it.count; i ++) {
|
||||
ecs_strbuf_list_next(&str);
|
||||
ecs_strbuf_list_push(&str, "{", ",");
|
||||
serialize_browse_info(world, e, it.entities[i], &str);
|
||||
ecs_strbuf_list_pop(&str, "}");
|
||||
}
|
||||
}
|
||||
ecs_strbuf_list_pop(&str, "]");
|
||||
|
||||
reply->body = ecs_strbuf_get(&str);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static
|
||||
bool endpoint_info(
|
||||
ecs_world_t *world,
|
||||
ecs_entity_t entity,
|
||||
EcsHttpEndpoint *endpoint,
|
||||
EcsHttpRequest *request,
|
||||
EcsHttpReply *reply)
|
||||
{
|
||||
ecs_entity_t e;
|
||||
|
||||
if (!parse_entity(world, request, &e)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ecs_strbuf_t str = ECS_STRBUF_INIT;
|
||||
ecs_strbuf_list_push(&str, "{", ",");
|
||||
serialize_browse_info(world, e, e, &str);
|
||||
ecs_strbuf_list_pop(&str, "}");
|
||||
|
||||
reply->body = ecs_strbuf_get(&str);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static
|
||||
void EcsRestSetServer(ecs_iter_t *it) {
|
||||
EcsRestServer *server_data = ecs_column(it, EcsRestServer, 1);
|
||||
ecs_entity_t ecs_typeid(EcsHttpServer) = ecs_column_entity(it, 2);
|
||||
ecs_entity_t ecs_typeid(EcsHttpEndpoint) = ecs_column_entity(it, 3);
|
||||
ecs_entity_t EcsRestInitialized = ecs_column_entity(it, 4);
|
||||
|
||||
ecs_world_t *world = it->world;
|
||||
|
||||
int i;
|
||||
for (i = 0; i < it->count; i ++) {
|
||||
ecs_entity_t server = it->entities[i];
|
||||
|
||||
// Get filtered list of entities
|
||||
ecs_set(world, server, EcsHttpServer, {.port = server_data[i].port});
|
||||
|
||||
if (ecs_has_entity(world, server, EcsRestInitialized)) {
|
||||
/* Don't add endpoints more than once */
|
||||
continue;
|
||||
}
|
||||
|
||||
ecs_entity_t e_filter = ecs_new_w_entity(world, ECS_CHILDOF | server);
|
||||
ecs_set(world, e_filter, EcsName, {"e_filter"});
|
||||
ecs_set(world, e_filter, EcsHttpEndpoint, {
|
||||
.url = "filter",
|
||||
.action = endpoint_filter,
|
||||
.synchronous = true,
|
||||
.ctx = NULL
|
||||
});
|
||||
|
||||
// Get filtered list of children
|
||||
ecs_entity_t e_scope = ecs_new_w_entity(world, ECS_CHILDOF | server);
|
||||
ecs_set(world, e_scope, EcsName, {"e_scope"});
|
||||
ecs_set(world, e_scope, EcsHttpEndpoint, {
|
||||
.url = "scope",
|
||||
.action = endpoint_scope,
|
||||
.synchronous = true,
|
||||
.ctx = NULL
|
||||
});
|
||||
|
||||
// Get component data for single entity
|
||||
ecs_entity_t e_entity = ecs_new_w_entity(world, ECS_CHILDOF | server);
|
||||
ecs_set(world, e_entity, EcsName, {"e_entity"});
|
||||
ecs_set(world, e_entity, EcsHttpEndpoint, {
|
||||
.url = "entity",
|
||||
.action = endpoint_entity,
|
||||
.synchronous = true,
|
||||
.ctx = NULL
|
||||
});
|
||||
|
||||
// Browse entity information
|
||||
ecs_entity_t e_browse = ecs_new_w_entity(world, ECS_CHILDOF | server);
|
||||
ecs_set(world, e_browse, EcsName, {"e_browse"});
|
||||
ecs_set(world, e_browse, EcsHttpEndpoint, {
|
||||
.url = "browse",
|
||||
.action = endpoint_browse,
|
||||
.synchronous = true,
|
||||
.ctx = NULL
|
||||
});
|
||||
|
||||
// Browse entity information for single entity
|
||||
ecs_entity_t e_info = ecs_new_w_entity(world, ECS_CHILDOF | server);
|
||||
ecs_set(world, e_info, EcsName, {"e_info"});
|
||||
ecs_set(world, e_info, EcsHttpEndpoint, {
|
||||
.url = "info",
|
||||
.action = endpoint_info,
|
||||
.synchronous = true,
|
||||
.ctx = NULL
|
||||
});
|
||||
|
||||
ecs_add_entity(world, server, EcsRestInitialized);
|
||||
}
|
||||
}
|
||||
|
||||
void FlecsRestImport(
|
||||
ecs_world_t *world)
|
||||
{
|
||||
ECS_MODULE(world, FlecsRest);
|
||||
|
||||
ecs_set_name_prefix(world, "EcsRest");
|
||||
|
||||
ECS_IMPORT(world, FlecsMeta);
|
||||
ECS_IMPORT(world, FlecsComponentsHttp);
|
||||
|
||||
ECS_META(world, EcsRestServer);
|
||||
ECS_TAG(world, EcsRestInitialized);
|
||||
|
||||
ECS_SYSTEM(world, EcsRestSetServer, EcsOnSet, Server,
|
||||
:flecs.components.http.Server,
|
||||
:flecs.components.http.Endpoint,
|
||||
:Initialized);
|
||||
|
||||
ECS_EXPORT_COMPONENT(EcsRestServer);
|
||||
}
|
|
@ -0,0 +1,95 @@
|
|||
#define flecs_rest_STATIC
|
||||
#ifndef FLECS_SYSTEMS_REST_H
|
||||
#define FLECS_SYSTEMS_REST_H
|
||||
|
||||
/* This generated file contains includes for project dependencies */
|
||||
/*
|
||||
)
|
||||
(.)
|
||||
.|.
|
||||
| |
|
||||
_.--| |--._
|
||||
.-'; ;`-'& ; `&.
|
||||
\ & ; & &_/
|
||||
|"""---...---"""|
|
||||
\ | | | | | | | /
|
||||
`---.|.|.|.---'
|
||||
|
||||
* This file is generated by bake.lang.c for your convenience. Headers of
|
||||
* dependencies will automatically show up in this file. Include bake_config.h
|
||||
* in your main project file. Do not edit! */
|
||||
|
||||
#ifndef FLECS_REST_BAKE_CONFIG_H
|
||||
#define FLECS_REST_BAKE_CONFIG_H
|
||||
|
||||
/* Headers of public dependencies */
|
||||
#include <flecs.h>
|
||||
#include <flecs_components_http.h>
|
||||
#include <flecs_meta.h>
|
||||
#include <flecs_json.h>
|
||||
|
||||
/* Convenience macro for exporting symbols */
|
||||
#ifndef flecs_rest_STATIC
|
||||
#if flecs_rest_EXPORTS && (defined(_MSC_VER) || defined(__MINGW32__))
|
||||
#define FLECS_REST_API __declspec(dllexport)
|
||||
#elif flecs_rest_EXPORTS
|
||||
#define FLECS_REST_API __attribute__((__visibility__("default")))
|
||||
#elif defined _MSC_VER
|
||||
#define FLECS_REST_API __declspec(dllimport)
|
||||
#else
|
||||
#define FLECS_REST_API
|
||||
#endif
|
||||
#else
|
||||
#define FLECS_REST_API
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
ECS_STRUCT(EcsRestServer, {
|
||||
int16_t port;
|
||||
});
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef struct FlecsRest {
|
||||
ECS_DECLARE_COMPONENT(EcsRestServer);
|
||||
} FlecsRest;
|
||||
|
||||
FLECS_REST_API
|
||||
void FlecsRestImport(
|
||||
ecs_world_t *world);
|
||||
|
||||
#define FlecsRestImportHandles(handles) \
|
||||
ECS_IMPORT_COMPONENT(handles, EcsRestServer);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
#ifndef FLECS_NO_CPP
|
||||
|
||||
namespace flecs {
|
||||
|
||||
class rest : public FlecsRest {
|
||||
public:
|
||||
using Server = EcsRestServer;
|
||||
|
||||
rest(flecs::world& world) {
|
||||
FlecsRestImport(world.c_ptr());
|
||||
|
||||
flecs::module<flecs::rest>(world, "flecs::rest");
|
||||
|
||||
flecs::component<Server>(world, "Server");
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // FLECS_NO_CPP
|
||||
#endif // __cplusplus
|
||||
|
||||
#endif
|
|
@ -0,0 +1,88 @@
|
|||
#define flecs_systems_civetweb_STATIC
|
||||
#ifndef FLECS_SYSTEMS_CIVETWEB_H
|
||||
#define FLECS_SYSTEMS_CIVETWEB_H
|
||||
|
||||
/*
|
||||
)
|
||||
(.)
|
||||
.|.
|
||||
| |
|
||||
_.--| |--._
|
||||
.-'; ;`-'& ; `&.
|
||||
\ & ; & &_/
|
||||
|"""---...---"""|
|
||||
\ | | | | | | | /
|
||||
`---.|.|.|.---'
|
||||
|
||||
* This file is generated by bake.lang.c for your convenience. Headers of
|
||||
* dependencies will automatically show up in this file. Include bake_config.h
|
||||
* in your main project file. Do not edit! */
|
||||
|
||||
#ifndef FLECS_SYSTEMS_CIVETWEB_BAKE_CONFIG_H
|
||||
#define FLECS_SYSTEMS_CIVETWEB_BAKE_CONFIG_H
|
||||
|
||||
/* Headers of public dependencies */
|
||||
#include <flecs.h>
|
||||
#include <flecs_components_http.h>
|
||||
|
||||
/* Convenience macro for exporting symbols */
|
||||
#ifndef flecs_systems_civetweb_STATIC
|
||||
#if flecs_systems_civetweb_EXPORTS && (defined(_MSC_VER) || defined(__MINGW32__))
|
||||
#define FLECS_SYSTEMS_CIVETWEB_API __declspec(dllexport)
|
||||
#elif flecs_systems_civetweb_EXPORTS
|
||||
#define FLECS_SYSTEMS_CIVETWEB_API __attribute__((__visibility__("default")))
|
||||
#elif defined _MSC_VER
|
||||
#define FLECS_SYSTEMS_CIVETWEB_API __declspec(dllimport)
|
||||
#else
|
||||
#define FLECS_SYSTEMS_CIVETWEB_API
|
||||
#endif
|
||||
#else
|
||||
#define FLECS_SYSTEMS_CIVETWEB_API
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef struct FlecsSystemsCivetweb {
|
||||
ECS_DECLARE_ENTITY(CivetServer);
|
||||
} FlecsSystemsCivetweb;
|
||||
|
||||
FLECS_SYSTEMS_CIVETWEB_API
|
||||
void FlecsSystemsCivetwebImport(
|
||||
ecs_world_t *world);
|
||||
|
||||
#define FlecsSystemsCivetwebImportHandles(handles)\
|
||||
ECS_IMPORT_ENTITY(handles, CivetServer);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
#ifndef FLECS_NO_CPP
|
||||
|
||||
namespace flecs {
|
||||
namespace systems {
|
||||
|
||||
class civetweb : public FlecsSystemsCivetweb {
|
||||
public:
|
||||
|
||||
civetweb(flecs::world& world) {
|
||||
FlecsSystemsCivetwebImport(world.c_ptr());
|
||||
|
||||
flecs::module<flecs::systems::civetweb>(
|
||||
world, "flecs::systems::civetweb");
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif // FLECS_NO_CPP
|
||||
#endif // __cplusplus
|
||||
|
||||
#endif
|
|
@ -0,0 +1,97 @@
|
|||
|
||||
Vue.component('app-browser', {
|
||||
data: function() {
|
||||
return {
|
||||
scope: "",
|
||||
entity: "",
|
||||
entity_validated: "",
|
||||
entity_components: {},
|
||||
entity_request: undefined,
|
||||
error: false
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
e_select_entity(event) {
|
||||
this.entity = event.entity;
|
||||
this.$refs.editor.select_entity(event.entity);
|
||||
},
|
||||
e_select_scope(event) {
|
||||
this.scope = event.scope;
|
||||
this.$refs.data.select_scope(event.scope);
|
||||
},
|
||||
e_select_component(event) {
|
||||
this.$refs.component_search.activate(event);
|
||||
}
|
||||
},
|
||||
template: `
|
||||
<div class="browser">
|
||||
<link rel="stylesheet" href="apps/browser/style.css">
|
||||
|
||||
<div clas="entity-tree-container">
|
||||
<entity-tree
|
||||
v-on:select-entity="e_select_entity"
|
||||
v-on:select-scope="e_select_scope"
|
||||
:show_nav="true"
|
||||
:auto_update="true">
|
||||
</entity-tree>
|
||||
</div>
|
||||
|
||||
<div class="entity-data-container">
|
||||
<entity-data
|
||||
:scope="scope"
|
||||
ref="data">
|
||||
</entity-data>
|
||||
</div>
|
||||
|
||||
<entity-editor
|
||||
:entity="entity"
|
||||
:components="entity_components"
|
||||
v-on:select-component="e_select_component"
|
||||
ref="editor">
|
||||
</entity-editor>
|
||||
|
||||
<component-search ref="component_search">
|
||||
</component-search>
|
||||
</div>
|
||||
`
|
||||
});
|
||||
|
||||
// Signal app has loaded & pass on dependencies
|
||||
app.app_loaded("browser", [{
|
||||
name: "entity-tree",
|
||||
url: "apps/browser/entity_tree.js"
|
||||
}, {
|
||||
name: "entity-tree-item",
|
||||
url: "apps/browser/entity_tree_item.js"
|
||||
}, {
|
||||
name: "entity-search",
|
||||
url: "apps/browser/entity_search.js"
|
||||
}, {
|
||||
name: "entity-kind",
|
||||
url: "apps/browser/entity_kind.js"
|
||||
}, {
|
||||
name: "entity-editor",
|
||||
url: "apps/browser/entity_editor.js"
|
||||
}, {
|
||||
name: "entity-component",
|
||||
url: "apps/browser/entity_component.js"
|
||||
}, {
|
||||
name: "component-properties",
|
||||
url: "apps/browser/component_properties.js"
|
||||
}, {
|
||||
name: "property-value",
|
||||
url: "apps/browser/property_value.js"
|
||||
}, {
|
||||
name: "entity-tag",
|
||||
url: "apps/browser/entity_tag.js"
|
||||
}, {
|
||||
name: "component-search",
|
||||
url: "apps/browser/component_search.js"
|
||||
}, {
|
||||
name: "entity-data",
|
||||
url: "apps/browser/entity_data.js"
|
||||
}, {
|
||||
name: "entity-table",
|
||||
url: "apps/browser/entity_table.js"
|
||||
}]
|
||||
);
|
|
@ -0,0 +1,13 @@
|
|||
|
||||
Vue.component('component-properties', {
|
||||
props: [ "name", "value" ],
|
||||
methods: {
|
||||
},
|
||||
template: `
|
||||
<div>
|
||||
<div class="component-key">{{ name }}</div>
|
||||
<property-value :value="value"></property-value>
|
||||
</div>
|
||||
`
|
||||
});
|
||||
|
|
@ -0,0 +1,48 @@
|
|||
|
||||
Vue.component('component-search', {
|
||||
data: function() {
|
||||
return {
|
||||
entity: "",
|
||||
visible: false,
|
||||
callback: undefined
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
activate(event) {
|
||||
this.entity = "";
|
||||
this.$refs.tree.reset();
|
||||
this.visible = true;
|
||||
this.callback = event.callback;
|
||||
},
|
||||
deactivate() {
|
||||
this.visible = false;
|
||||
this.callback = undefined;
|
||||
},
|
||||
e_set_entity(event) {
|
||||
this.callback(event.entity);
|
||||
this.deactivate();
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
visibility: function() {
|
||||
if (this.visible) {
|
||||
return "visible";
|
||||
} else {
|
||||
return "hidden";
|
||||
}
|
||||
}
|
||||
},
|
||||
template: `
|
||||
<div class="component-search-darken" :style="'visibility: ' + visibility" v-on:click="deactivate">
|
||||
<div class="component-search" v-on:click.stop="">
|
||||
<entity-tree
|
||||
v-on:select-entity="e_set_entity"
|
||||
:filter="['Component', 'Module']"
|
||||
:auto_update="false"
|
||||
search_text="Find a component"
|
||||
ref="tree">
|
||||
</entity-tree>
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
});
|
|
@ -0,0 +1,20 @@
|
|||
|
||||
Vue.component('entity-component', {
|
||||
props: [ "name", "value" ],
|
||||
methods: {
|
||||
remove_component() {
|
||||
this.$emit('remove_component', {component: this.name});
|
||||
}
|
||||
},
|
||||
template: `
|
||||
<div class="entity-component">
|
||||
<div class="entity-component-name">{{ name }}</div>
|
||||
<img src="images/delete.png" class="entity-remove-icon" v-on:click="remove_component">
|
||||
|
||||
<component-properties v-for="(v, name) in value" class="component" :key="name"
|
||||
:name="name"
|
||||
:value="v">
|
||||
</component-properties>
|
||||
</div>
|
||||
`
|
||||
});
|
|
@ -0,0 +1,64 @@
|
|||
|
||||
Vue.component('entity-data', {
|
||||
props: [ "filter" ],
|
||||
mounted: function() {
|
||||
this.startRequesting("");
|
||||
},
|
||||
beforeDestroy: function() {
|
||||
this.stopRequesting();
|
||||
},
|
||||
data: function() {
|
||||
return {
|
||||
scope: "",
|
||||
scope_pending: "",
|
||||
data: [],
|
||||
data_request: undefined,
|
||||
error: false
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
request_data(scope) {
|
||||
var url = "scope/" + scope + "?include=Name";
|
||||
if (this.filter) {
|
||||
url += "," + this.filter.join(",");
|
||||
}
|
||||
|
||||
app.get(url, (msg) => {
|
||||
this.data = msg;
|
||||
this.error = false;
|
||||
this.scope = this.scope_pending;
|
||||
}, (Http) => {
|
||||
if (Http.status == 404) {
|
||||
this.error = true;
|
||||
}
|
||||
});
|
||||
},
|
||||
// Stop periodically requesting the scope
|
||||
stopRequesting() {
|
||||
this.data = [];
|
||||
clearInterval(this.data_request);
|
||||
},
|
||||
// Start periodically requesting a scope
|
||||
startRequesting(scope) {
|
||||
this.stopRequesting();
|
||||
|
||||
// Initial request
|
||||
var scope_url = scope.replace(/\./g, "/");
|
||||
this.request_data(scope_url);
|
||||
|
||||
// Start periodic request
|
||||
this.data_request = window.setInterval(function() {
|
||||
this.request_data(scope_url);
|
||||
}.bind(this), 1000);
|
||||
},
|
||||
select_scope(scope) {
|
||||
this.scope_pending = scope;
|
||||
this.startRequesting(scope);
|
||||
}
|
||||
},
|
||||
template: `
|
||||
<div>
|
||||
<entity-table v-for="table in data" :data="table" :key="table.type.join(',')"></entity-table>
|
||||
</div>
|
||||
`
|
||||
});
|
|
@ -0,0 +1,128 @@
|
|||
|
||||
Vue.component('entity-editor', {
|
||||
data: function() {
|
||||
return {
|
||||
entity_pending: "",
|
||||
entity: "",
|
||||
components: {},
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
entity_url(entity) {
|
||||
return entity.replace(/\./g, "/");
|
||||
},
|
||||
request_entity(entity) {
|
||||
if (!entity || !entity.length) {
|
||||
this.entity_pending = "";
|
||||
this.entity = "";
|
||||
this.components = {};
|
||||
return;
|
||||
}
|
||||
|
||||
this.entity_pending = entity;
|
||||
var entity_url = this.entity_url(entity);
|
||||
|
||||
app.get("entity/" + entity_url, (msg) => {
|
||||
this.entity = this.entity_pending;
|
||||
this.components = msg;
|
||||
this.error = false;
|
||||
}, (Http) => {
|
||||
if (Http.status == 404) {
|
||||
// Entity can no longer be found on URL
|
||||
this.entity_pending = "";
|
||||
this.entity = "";
|
||||
this.components = {};
|
||||
}
|
||||
});
|
||||
},
|
||||
select_entity(entity) {
|
||||
if (entity != this.entity) {
|
||||
this.entity_pending = entity;
|
||||
this.request_entity(entity);
|
||||
}
|
||||
},
|
||||
entity_hidden() {
|
||||
if (this.entity) {
|
||||
return 'visible;';
|
||||
} else {
|
||||
return 'hidden;';
|
||||
}
|
||||
},
|
||||
components_hidden() {
|
||||
if (this.components) {
|
||||
return 'visible;';
|
||||
} else {
|
||||
return 'hidden;';
|
||||
}
|
||||
},
|
||||
e_add(event) {
|
||||
this.$emit('select-component', {
|
||||
callback: function(component) {
|
||||
app.put("entity/" + this.entity_url(this.entity) + "?select=" + component, undefined, (msg) => {
|
||||
this.request_entity(this.entity);
|
||||
});
|
||||
}.bind(this)
|
||||
});
|
||||
},
|
||||
e_sync(event) {
|
||||
this.request_entity(this.entity);
|
||||
},
|
||||
e_remove_component(event) {
|
||||
app.delete("entity/" + this.entity_url(this.entity) + "?select=" + event.component, undefined, (msg) => {
|
||||
this.request_entity(this.entity);
|
||||
});
|
||||
},
|
||||
e_delete(event) {
|
||||
app.delete("entity/" + this.entity_url(this.entity), undefined, (msg) => {
|
||||
this.request_entity(undefined);
|
||||
});
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
tags: function() {
|
||||
var type = this.components.type;
|
||||
if (!type) {
|
||||
return [];
|
||||
}
|
||||
|
||||
var data = this.components.data;
|
||||
var tags = [];
|
||||
for (var i = 0; i < type.length; i ++) {
|
||||
var tag = type[i];
|
||||
if (!data.hasOwnProperty(tag)) {
|
||||
if (Array.isArray(tag)) {
|
||||
tags.push(type[i].join(" | "));
|
||||
} else {
|
||||
tags.push(tag);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return tags;
|
||||
}
|
||||
},
|
||||
template: `
|
||||
<div>
|
||||
<div class="entity-editor" :style="'visibility: ' + entity_hidden()">
|
||||
<div class="entity-name">
|
||||
{{ components.path }}
|
||||
<img src="images/sync.png" class="entity-icon" v-on:click="e_sync">
|
||||
<img src="images/add.png" class="entity-icon" v-on:click="e_add">
|
||||
<img src="images/delete.png" class="entity-remove-icon" v-on:click="e_delete">
|
||||
</div>
|
||||
|
||||
<div v-for="(value, key) in components.data" :style="'visibility: ' + components_hidden()">
|
||||
<entity-component :name="key" :value="value" v-on:remove_component="e_remove_component">
|
||||
</entity-component>
|
||||
</div>
|
||||
|
||||
<div class="entity-component-name" v-if="tags.length">Tags</div>
|
||||
<div class="entity-tags">
|
||||
<div v-for="tag in tags" :style="'visibility: ' + components_hidden()">
|
||||
<entity-tag :name="tag" v-on:remove_component="e_remove_component"></entity-tag>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
});
|
|
@ -0,0 +1,36 @@
|
|||
|
||||
Vue.component('entity-kind', {
|
||||
props: [ "kind" ],
|
||||
methods: {
|
||||
css() {
|
||||
var result = "entity-kind";
|
||||
if (this.kind && this.kind.length) {
|
||||
result += " entity-kind-" + this.kind;
|
||||
}
|
||||
return result;
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
kind_string: function() {
|
||||
if (this.kind == "Module") {
|
||||
return "m";
|
||||
} else if (this.kind == "Component") {
|
||||
return "c";
|
||||
} else if (this.kind == "System") {
|
||||
return "s";
|
||||
} else if (this.kind == "Type") {
|
||||
return "t";
|
||||
} else if (this.kind == "Prefab") {
|
||||
return "p";
|
||||
} else {
|
||||
return "e";
|
||||
}
|
||||
}
|
||||
},
|
||||
template: `
|
||||
<span :class="css()">
|
||||
{{ kind_string }}
|
||||
</span>
|
||||
`
|
||||
});
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
|
||||
Vue.component('entity-search', {
|
||||
props: [ "error", "search_text", "found" ],
|
||||
data: function() {
|
||||
return {
|
||||
search_scope: ""
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
css() {
|
||||
var result = "entity-search";
|
||||
if (this.valid) {
|
||||
result += " entity-search-found";
|
||||
}
|
||||
return result;
|
||||
},
|
||||
add_entity: function(e) {
|
||||
this.$emit('add-entity', {entity: this.search_scope});
|
||||
},
|
||||
changed: function(e) {
|
||||
this.$emit('changed', {entity: e.target.value});
|
||||
},
|
||||
set_value(scope) {
|
||||
this.search_scope = scope;
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
default_text: function() {
|
||||
if (this.search_text) {
|
||||
return this.search_text;
|
||||
} else {
|
||||
return "Find an entity";
|
||||
}
|
||||
},
|
||||
valid: function() {
|
||||
return this.found && this.search_scope.length;
|
||||
}
|
||||
},
|
||||
template: `
|
||||
<div :class="css()">
|
||||
<input v-on:keyup="changed" v-model="search_scope" :placeholder="default_text"></input>
|
||||
<img src="images/add.png" class="entity-remove-icon" v-if="!found && search_scope.length" v-on:click="add_entity">
|
||||
</div>
|
||||
`
|
||||
});
|
|
@ -0,0 +1,85 @@
|
|||
|
||||
Vue.component('entity-table', {
|
||||
props: [ "data" ],
|
||||
methods: {
|
||||
component_headers: function(name) {
|
||||
const value = this.data.data[name][0];
|
||||
let headers = [];
|
||||
for (key in value) {
|
||||
headers.push(key);
|
||||
}
|
||||
return headers;
|
||||
},
|
||||
component_member: function(value) {
|
||||
if (Array.isArray(value)) {
|
||||
return "[...]";
|
||||
} else if (typeof value === 'object') {
|
||||
return "{...}";
|
||||
} else if (typeof value === 'number') {
|
||||
return value.toFixed(1);
|
||||
} else {
|
||||
return value;
|
||||
}
|
||||
},
|
||||
type: function(value) {
|
||||
if (Array.isArray(value)) {
|
||||
return 'array';
|
||||
} else if (typeof value === 'object') {
|
||||
return 'object';
|
||||
} else if (typeof value === 'number') {
|
||||
return 'number';
|
||||
} else if (typeof value === 'boolean') {
|
||||
return 'boolean';
|
||||
} else if (typeof value === 'string') {
|
||||
return 'string';
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
},
|
||||
value_css: function(value) {
|
||||
return "property-value-" + this.type(value);
|
||||
}
|
||||
},
|
||||
template: `
|
||||
<div class="entity-table">
|
||||
<div class="entity-table-body">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="table-component">Id</th>
|
||||
<th class="table-separator"></th>
|
||||
<th class="table-component">Name</th>
|
||||
<template v-for="(component,key) in data.data" v-if="key != 'Name'">
|
||||
<th class="table-separator"></th>
|
||||
<th class="table-component" :colspan="component_headers(key).length"> {{ key }} </th>
|
||||
</template>
|
||||
</tr>
|
||||
<tr>
|
||||
<th class="table-member"></th>
|
||||
<th class="table-separator"></th>
|
||||
<th class="table-member"></th>
|
||||
<template v-for="(component, name) in data.data" v-if="name != 'Name'">
|
||||
<th class="table-separator"></th>
|
||||
<th class="table-member" v-for="member in component_headers(name)">{{ member }}</th>
|
||||
</template>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="(entity, i) in data.entities">
|
||||
<td class="table-value">{{ entity }}</td>
|
||||
<td class="table-separator"></td>
|
||||
<td class="table-value">{{ data.data.Name[i].value }}</td>
|
||||
<template v-for="(component,key) in data.data" v-if="key != 'Name'">
|
||||
<td class="table-separator"></td>
|
||||
<td class="table-value" v-for="member in component_headers(key)" :class="value_css(component[i][member])">
|
||||
{{ component_member(component[i][member]) }}
|
||||
</td>
|
||||
</template>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
});
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
|
||||
Vue.component('entity-tag', {
|
||||
props: [ "name" ],
|
||||
methods: {
|
||||
remove_component() {
|
||||
this.$emit('remove_component', {component: this.name});
|
||||
}
|
||||
},
|
||||
template: `
|
||||
<span class="entity-tag">
|
||||
{{ name }}
|
||||
<img src="images/close.png" class="entity-remove-icon" v-on:click="remove_component">
|
||||
</span>
|
||||
`
|
||||
});
|
||||
|
|
@ -0,0 +1,207 @@
|
|||
// Component that implements a traversable entity tree
|
||||
Vue.component('entity-tree', {
|
||||
props: {
|
||||
filter: Array,
|
||||
search_text: String,
|
||||
auto_update: Boolean
|
||||
},
|
||||
mounted: function() {
|
||||
this.startRequesting("");
|
||||
},
|
||||
beforeDestroy: function() {
|
||||
this.stopRequesting();
|
||||
},
|
||||
data: function() {
|
||||
return {
|
||||
scope_pending: "",
|
||||
scope: "",
|
||||
scope_info: {},
|
||||
scope_request: undefined,
|
||||
entities: [],
|
||||
entity: "",
|
||||
error: false
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// Send an HTTP request for the current scope
|
||||
request_scope(scope) {
|
||||
var url = "browse/" + scope + "?include=Name";
|
||||
if (this.filter) {
|
||||
url += "," + this.filter.join(",");
|
||||
}
|
||||
app.get(url, (msg) => {
|
||||
if (this.scope != this.scope_pending) {
|
||||
this.$emit("select-scope", {scope: this.scope_pending});
|
||||
}
|
||||
this.scope = this.scope_pending;
|
||||
this.entities = msg;
|
||||
this.error = false;
|
||||
}, (Http) => {
|
||||
if (Http.status == 404) {
|
||||
this.error = true;
|
||||
}
|
||||
});
|
||||
},
|
||||
// Send an HTTP request for the current scope
|
||||
request_scope_info(scope) {
|
||||
var filter = ""
|
||||
if (this.filter) {
|
||||
filter = "?include=" + this.filter.join(",");
|
||||
}
|
||||
app.get("info/" + scope + filter, (msg) => {
|
||||
this.scope_info = msg;
|
||||
}, (Http) => {
|
||||
if (Http.status == 404) {
|
||||
this.error = true;
|
||||
}
|
||||
});
|
||||
},
|
||||
// Send an HTTP request for the current scope
|
||||
request_add_entity(entity) {
|
||||
app.post("entity/" + entity, (msg) => {
|
||||
}, (Http) => {
|
||||
if (Http.status == 404) {
|
||||
this.error = true;
|
||||
}
|
||||
});
|
||||
},
|
||||
// Stop periodically requesting the scope
|
||||
stopRequesting() {
|
||||
this.scope = undefined;
|
||||
this.scope_validated = undefined;
|
||||
clearInterval(this.scope_request);
|
||||
},
|
||||
// Start periodically requesting a scope
|
||||
startRequesting(scope) {
|
||||
this.stopRequesting();
|
||||
|
||||
// Initial request
|
||||
var scope_url = scope.replace(/\./g, "/");
|
||||
this.request_scope(scope_url);
|
||||
this.request_scope_info(scope_url);
|
||||
|
||||
// Start periodic request
|
||||
if (this.auto_update) {
|
||||
this.scope_request = window.setInterval(function() {
|
||||
this.request_scope(scope_url);
|
||||
}.bind(this), 1000);
|
||||
}
|
||||
},
|
||||
// Reset to initial state
|
||||
reset() {
|
||||
this.entity = "";
|
||||
this.entities = [];
|
||||
this.scope = "";
|
||||
this.scope_pending = "";
|
||||
this.$refs.search.set_value("");
|
||||
|
||||
if (!this.auto_update) {
|
||||
this.startRequesting(this.scope);
|
||||
}
|
||||
},
|
||||
// Change current scope to fully qualified path
|
||||
set_scope(scope) {
|
||||
this.scope_pending = scope;
|
||||
this.$refs.search.set_value(scope);
|
||||
this.startRequesting(scope);
|
||||
|
||||
if (this.entity != "") {
|
||||
this.select_entity("");
|
||||
}
|
||||
},
|
||||
// Event that contains an full path
|
||||
e_set_scope: function(event) {
|
||||
this.set_scope(event.entity);
|
||||
},
|
||||
// Event that contains a relative entity id
|
||||
e_set_scope_relative: function(event) {
|
||||
var scope = "";
|
||||
|
||||
if (this.scope && this.scope != "") {
|
||||
scope = this.scope + "." + event.entity;
|
||||
} else {
|
||||
scope = event.entity;
|
||||
}
|
||||
|
||||
this.set_scope(scope);
|
||||
},
|
||||
|
||||
// Select an entity from the tree
|
||||
select_entity(entity) {
|
||||
if (entity == this.entity) {
|
||||
this.entity = "";
|
||||
} else {
|
||||
this.entity = entity;
|
||||
}
|
||||
|
||||
this.$emit("select-entity", {entity: this.entity});
|
||||
},
|
||||
// Select the current scope
|
||||
e_select_scope: function() {
|
||||
this.select_entity(this.scope);
|
||||
},
|
||||
e_add_entity: function(e) {
|
||||
this.request_add_entity(e.entity);
|
||||
this.scope = this.scope_pending;
|
||||
var parent = this.parent;
|
||||
this.scope_pending = parent;
|
||||
this.startRequesting(parent);
|
||||
this.$refs.search.set_value(parent);
|
||||
},
|
||||
// Select a relative entity identifier
|
||||
e_select_entity_relative: function(event) {
|
||||
var entity;
|
||||
if (this.scope) {
|
||||
entity = this.scope + "." + event.entity;
|
||||
} else {
|
||||
entity = event.entity;
|
||||
}
|
||||
this.select_entity(entity);
|
||||
},
|
||||
entity_tree_css() {
|
||||
var result = "entity-tree-list";
|
||||
if (this.show_nav) {
|
||||
result += " entity-tree-list-container";
|
||||
}
|
||||
return result;
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
parent: function() {
|
||||
if (this.scope && this.scope.indexOf(".")) {
|
||||
return this.scope.substr(0, this.scope.lastIndexOf("."));
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
},
|
||||
template: `
|
||||
<div class="entity-tree">
|
||||
<entity-search
|
||||
:found="scope === scope_pending"
|
||||
:search_text="search_text"
|
||||
v-on:changed="e_set_scope"
|
||||
v-on:add-entity="e_add_entity"
|
||||
ref="search">
|
||||
</entity-search>
|
||||
|
||||
<div :class="entity_tree_css()">
|
||||
<entity-tree-item :entity="scope_info" :text="'.'" :current="entity" v-if="scope && scope.length"
|
||||
v-on:select-entity="e_select_scope">
|
||||
</entity-tree-item>
|
||||
|
||||
<div class="entity-tree-separator" v-if="scope && scope.length">
|
||||
</div>
|
||||
|
||||
<entity-tree-item :entity="{'name':parent, 'type':[]}" :text="'..'" nav_only="true" v-if="scope && scope.length"
|
||||
v-on:nav-scope="e_set_scope">
|
||||
</entity-tree-item>
|
||||
|
||||
<entity-tree-item v-for="(e, i) in entities" :entity="e" :current="entity" :key="e.name"
|
||||
v-on:nav-scope="e_set_scope_relative"
|
||||
v-on:select-entity="e_select_entity_relative">
|
||||
</entity-tree-item>
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
});
|
|
@ -0,0 +1,121 @@
|
|||
|
||||
Vue.component('entity-tree-item', {
|
||||
props: [ "entity", "current", "text", "nav_only", "" ],
|
||||
data: function() {
|
||||
return {
|
||||
clicks: 0,
|
||||
timer: null
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
nav_scope: function() {
|
||||
this.$emit("nav-scope", {entity: this.entity.name});
|
||||
},
|
||||
|
||||
single_click_nav_scope: function() {
|
||||
if (this.nav_only) {
|
||||
this.nav_scope();
|
||||
} else {
|
||||
this.$emit("select-entity", {entity: this.entity.name});
|
||||
}
|
||||
},
|
||||
|
||||
is_disabled() {
|
||||
for (var i = 0; i < this.entity.type.length; i ++) {
|
||||
var el = this.entity.type[i];
|
||||
|
||||
if (el == "Disabled") {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
|
||||
css() {
|
||||
var result = "entity-tree-item";
|
||||
|
||||
if (this.is_disabled()) {
|
||||
result += " entity-tree-item-disabled";
|
||||
}
|
||||
|
||||
if (!this.current) {
|
||||
return result;
|
||||
}
|
||||
|
||||
var entity = this.entity.name;
|
||||
var current;
|
||||
|
||||
if (this.current.indexOf(".") != -1) {
|
||||
var pos = this.current.lastIndexOf(".") + 1;
|
||||
current = this.current.substr(pos, this.current.length - pos);
|
||||
} else {
|
||||
current = this.current;
|
||||
}
|
||||
|
||||
if (entity === current) {
|
||||
result += " entity-tree-item-active";
|
||||
}
|
||||
|
||||
return result;
|
||||
},
|
||||
|
||||
kind() {
|
||||
var component = "";
|
||||
|
||||
for (var i = 0; i < this.entity.type.length; i ++) {
|
||||
var el = this.entity.type[i];
|
||||
|
||||
if (el == "Module") {
|
||||
component = el;
|
||||
break;
|
||||
} else if (el == "Component") {
|
||||
component = el;
|
||||
// Don't break, entity can also be module
|
||||
} else if (el == "Prefab") {
|
||||
component = el;
|
||||
break;
|
||||
} else if (el == "flecs.system.System") {
|
||||
component = "System";
|
||||
break;
|
||||
} else if (el == "Type") {
|
||||
component = el;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return component;
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
entity_id: function() {
|
||||
var text = this.text ? this.text : "";
|
||||
var entity = "";
|
||||
|
||||
if (this.entity) {
|
||||
entity = this.entity.name ? this.entity.name : "";
|
||||
}
|
||||
|
||||
// A bit of a convoluted way to makes sure that we show text instead of an
|
||||
// entity name when provided, except when the text is "." in which case we
|
||||
// show the entity name when there is one.
|
||||
if (text.length && (text != "." || !entity.length)) {
|
||||
return text;
|
||||
} else {
|
||||
return entity;
|
||||
}
|
||||
},
|
||||
show_nav() {
|
||||
return !this.nav_only && this.text != ".";
|
||||
}
|
||||
},
|
||||
template: `
|
||||
<div :class="css()" v-on:click.stop="single_click_nav_scope">
|
||||
<div>
|
||||
<img src="images/nav.png" class="entity-remove-icon" v-on:click.stop="nav_scope" v-if="show_nav && entity.child_count">
|
||||
<entity-kind :kind="kind()" v-if="entity_id.length"></entity-kind>
|
||||
<span class="entity-item-name">{{entity_id}}</span>
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
});
|
|
@ -0,0 +1,43 @@
|
|||
|
||||
Vue.component('property-value', {
|
||||
props: [ "value" ],
|
||||
methods: {
|
||||
css() {
|
||||
var result = "property-value";
|
||||
if (this.type.length) {
|
||||
result += " property-value-" + this.type;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
type: function() {
|
||||
if (Array.isArray(this.value)) {
|
||||
return 'array';
|
||||
} else if (typeof this.value === 'object') {
|
||||
return 'object';
|
||||
} else if (typeof this.value === 'number') {
|
||||
return 'number';
|
||||
} else if (typeof this.value === 'boolean') {
|
||||
return 'boolean';
|
||||
} else if (typeof this.value === 'string') {
|
||||
return 'string';
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
},
|
||||
template: `
|
||||
<div :class="css()">
|
||||
<div v-if="type === 'object'">
|
||||
|
||||
</div>
|
||||
<div v-else-if="type === 'array'">
|
||||
|
||||
</div>
|
||||
<div v-else>
|
||||
<input :value="value" spellcheck="false"></input>
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
});
|
|
@ -0,0 +1,420 @@
|
|||
div.browser {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
color: #eee;
|
||||
}
|
||||
|
||||
div.entity-tree-list-container {
|
||||
position: absolute;
|
||||
top: 60px;
|
||||
height: calc(100% - 200px);
|
||||
}
|
||||
|
||||
div.entity-data-container {
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
left: 270px;
|
||||
max-width: calc(100% - 275px);
|
||||
height: calc(100% - 10px);
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
div.entity-table {
|
||||
margin: 10px;
|
||||
}
|
||||
|
||||
div.entity-table-header {
|
||||
background-color: #1E2022;
|
||||
border-top-left-radius: 5px;
|
||||
border-top-right-radius: 5px;
|
||||
border-style: solid;
|
||||
border-color: #1E2022;
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
div.entity-table-body {
|
||||
padding-left: 20px;
|
||||
padding-right: 20px;
|
||||
padding-bottom: 20px;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
div.entity-table-body table {
|
||||
color: #eee;
|
||||
border-collapse: collapse;
|
||||
|
||||
background-color: #303136;
|
||||
border-radius: 5px;
|
||||
box-shadow: 3px 5px 10px rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
th.table-component {
|
||||
background-color: #1E2022;
|
||||
padding-top: 10px;
|
||||
padding-left: 16px;
|
||||
padding-right: 16px;
|
||||
text-transform: uppercase;
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
th.table-component:first-child {
|
||||
border-top-left-radius: 5px;
|
||||
}
|
||||
|
||||
th.table-component:last-child {
|
||||
border-top-right-radius: 5px;
|
||||
}
|
||||
|
||||
.table-separator {
|
||||
background-color: #37393E !important;
|
||||
padding: 0.5px !important;
|
||||
}
|
||||
|
||||
td.table-separator {
|
||||
border-top-color: #37393E;
|
||||
border-top-style: solid;
|
||||
border-top-width: 1px;
|
||||
}
|
||||
|
||||
th.table-member {
|
||||
background-color: #1E2022 !important;
|
||||
color: #aaa !important;
|
||||
text-align: center;
|
||||
padding-left: 8px;
|
||||
padding-right: 8px;
|
||||
padding-bottom: 5px;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
td.table-value {
|
||||
font-weight: 300;
|
||||
padding: 8px;
|
||||
padding-left: 10px;
|
||||
padding-right: 10px;
|
||||
text-align: center;
|
||||
white-space: nowrap;
|
||||
|
||||
border-top-color: #37393E;
|
||||
border-top-style: solid;
|
||||
border-top-width: 1px;
|
||||
}
|
||||
|
||||
div.entity-tree {
|
||||
position: relative;
|
||||
top: 0px;
|
||||
width: 250px;
|
||||
min-width: 200px;
|
||||
height: calc(100% - 20px);
|
||||
padding: 10px;
|
||||
background-color: #303136;
|
||||
margin: 0px;
|
||||
box-shadow: 5px 0px 8px rgba(0, 0, 0, 0.4);
|
||||
}
|
||||
|
||||
div.entity-tree-list {
|
||||
position: absolute;
|
||||
top: 60px;
|
||||
width: 250px;
|
||||
min-width: 250px;
|
||||
height: calc(100% - 60px);
|
||||
overflow-y: auto;
|
||||
margin: 0px;
|
||||
}
|
||||
|
||||
div.entity-tree-item {
|
||||
position: relative;
|
||||
font-size: 16px;
|
||||
padding-left: 10px;
|
||||
padding-right: 20px;
|
||||
padding-top: 10px;
|
||||
padding-bottom: 10px;
|
||||
min-height: 20px;
|
||||
cursor: pointer;
|
||||
|
||||
-webkit-touch-callout: none;
|
||||
-webkit-user-select: none;
|
||||
-khtml-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
div.entity-tree-separator {
|
||||
border-top-style: solid;
|
||||
border-top-color: #37393E;
|
||||
border-top-width: 1px;
|
||||
margin-top: 10px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
div.entity-tree-item:hover {
|
||||
background-color: #37393E;
|
||||
}
|
||||
|
||||
div.entity-tree-item-disabled {
|
||||
color: #aaa;
|
||||
}
|
||||
|
||||
div.entity-tree-item-active {
|
||||
background-color: #37393E;
|
||||
color: #eee;
|
||||
}
|
||||
|
||||
span.entity-item-name {
|
||||
padding-left: 30px;
|
||||
}
|
||||
|
||||
span.entity-kind {
|
||||
position: absolute;
|
||||
border-style: solid;
|
||||
border-width: 1px;
|
||||
border-color: #eee;
|
||||
border-radius: 4px;
|
||||
top: 11px;
|
||||
left: 15px;
|
||||
padding: 2px;
|
||||
min-width: 10px;
|
||||
min-height: 10px;
|
||||
font-size: 10px;
|
||||
text-align: center;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
span.entity-kind-Module {
|
||||
color: #C7CA6C;
|
||||
border-color: #C7CA6C;
|
||||
}
|
||||
|
||||
span.entity-kind-Component {
|
||||
color: #47B576;
|
||||
border-color: #47B576;
|
||||
}
|
||||
|
||||
span.entity-kind-System {
|
||||
color: #4981B5;
|
||||
border-color: #4981B5;
|
||||
}
|
||||
|
||||
span.entity-kind-Type {
|
||||
color: #4981B5;
|
||||
border-color: #4981B5;
|
||||
}
|
||||
|
||||
span.entity-kind-Prefab {
|
||||
color: #B5488F;
|
||||
border-color: #B5488F;
|
||||
}
|
||||
|
||||
div.entity-search {
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
div.entity-search input {
|
||||
background-color: #1E2022;
|
||||
border-radius: 5px;
|
||||
border-style: solid;
|
||||
border-width: 1px;
|
||||
border-color: #1E2022;
|
||||
margin: 2px;
|
||||
width: 246px;
|
||||
font-size: 16px;
|
||||
padding: 8px;
|
||||
color: #eee;
|
||||
transition: border-color 200ms;
|
||||
}
|
||||
|
||||
div.entity-search-found input {
|
||||
border-color: #47B576;
|
||||
}
|
||||
|
||||
p.list-header {
|
||||
position: absolute;
|
||||
text-transform: uppercase;
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
#scope-parent {
|
||||
position: absolute;
|
||||
top: 55px;
|
||||
width: 220px;
|
||||
}
|
||||
|
||||
#scope-entity {
|
||||
position: absolute;
|
||||
top: 95px;
|
||||
width: 220px;
|
||||
}
|
||||
|
||||
#header-children {
|
||||
top: 130px;
|
||||
}
|
||||
|
||||
div.entity-editor {
|
||||
background-color: #303136;
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
right: 0px;
|
||||
width: 350px;
|
||||
min-width: 350px;
|
||||
height: calc(100%);
|
||||
margin: 0px;
|
||||
box-shadow: 0px 0px 20px rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
div.entity-name {
|
||||
background-color: #1E2022;
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
padding: 10px;
|
||||
padding-top: 5px;
|
||||
}
|
||||
|
||||
div.entity-component {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
div.entity-component-buttons {
|
||||
position: relative;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
button {
|
||||
background-color: #eee;
|
||||
color: #1E2022;
|
||||
padding: 6px;
|
||||
margin-top: 5px;
|
||||
margin-left: 5px;
|
||||
border: none;
|
||||
font-size: 14px;
|
||||
cursor: pointer;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
div.entity-component-name {
|
||||
font-weight: bold;
|
||||
padding: 10px;
|
||||
margin-top: 20px;
|
||||
border-top-style: solid;
|
||||
border-top-width: 1px;
|
||||
border-top-color: #37393E;
|
||||
text-transform: uppercase;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
div.entity-component-value {
|
||||
padding: 5px;
|
||||
margin-top: 5px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
span.entity-tag {
|
||||
position: relative;
|
||||
background-color: #1E2022;
|
||||
padding: 8px;
|
||||
padding-right: 30px;
|
||||
margin: 2px;
|
||||
float: left;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
div.entity-tags {
|
||||
margin-left: 20px;
|
||||
}
|
||||
|
||||
img.entity-icon {
|
||||
position: relative;
|
||||
top: 3px;
|
||||
left: 5px;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
img.entity-remove-icon {
|
||||
position: absolute;
|
||||
top: 8px;
|
||||
right: 8px;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
div.component {
|
||||
position: relative;
|
||||
padding: 10px;
|
||||
padding-left: 20px;
|
||||
margin-top: 5px;
|
||||
height: 5px;
|
||||
}
|
||||
|
||||
div.component-key {
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
font-size: 14px;
|
||||
color: #aaa;
|
||||
}
|
||||
|
||||
div.property-value {
|
||||
position: absolute;
|
||||
top: -5px;
|
||||
left: 100px;
|
||||
width: calc(100% - 110px);
|
||||
}
|
||||
|
||||
div.property-value input {
|
||||
background-color: #1E2022;
|
||||
border-radius: 2px;
|
||||
border-style: solid;
|
||||
border-width: 1px;
|
||||
border-color: #1E2022;
|
||||
font-size: 14px;
|
||||
margin-bottom: 5px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
div.property-value-string input {
|
||||
color: #B56D48;
|
||||
}
|
||||
|
||||
div.property-value-number input {
|
||||
color: #67B588;
|
||||
}
|
||||
|
||||
div.property-value-boolean input {
|
||||
color: #4981B5;
|
||||
}
|
||||
|
||||
.property-value-string {
|
||||
color: #B56D48;
|
||||
}
|
||||
|
||||
.property-value-number {
|
||||
color: #67B588;
|
||||
}
|
||||
|
||||
.property-value-boolean {
|
||||
color: #4981B5;
|
||||
}
|
||||
|
||||
div.component-search-darken {
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
left: 0px;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
div.component-search {
|
||||
background-color: #303136;
|
||||
position: absolute;
|
||||
padding-bottom: 10px;
|
||||
top: 35px;
|
||||
right: 40px;
|
||||
height: 400px;
|
||||
max-width: 270px;
|
||||
box-shadow: 0px 5px 20px rgba(0, 0, 0, 0.5);
|
||||
}
|
|
@ -0,0 +1,122 @@
|
|||
const delta_time_chart = {
|
||||
type: 'line',
|
||||
data: {
|
||||
labels: [],
|
||||
datasets: [
|
||||
{
|
||||
label: 'avg',
|
||||
data: [],
|
||||
backgroundColor: [ 'rgba(0,0,0,0)' ],
|
||||
borderColor: [
|
||||
'#5BE595',
|
||||
],
|
||||
borderWidth: 2,
|
||||
pointRadius: 0
|
||||
},
|
||||
{
|
||||
label: 'min',
|
||||
data: [],
|
||||
backgroundColor: [ 'rgba(0,0,0,0)' ],
|
||||
borderColor: [
|
||||
'#40805B',
|
||||
],
|
||||
borderWidth: 1,
|
||||
pointRadius: 0
|
||||
},
|
||||
{
|
||||
label: 'max',
|
||||
data: [],
|
||||
backgroundColor: [ 'rgba(0,0,0,0)' ],
|
||||
borderColor: [
|
||||
'#40805B',
|
||||
],
|
||||
borderWidth: 1,
|
||||
pointRadius: 0
|
||||
}
|
||||
]
|
||||
},
|
||||
options: {
|
||||
title: {
|
||||
display: false
|
||||
},
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
lineTension: 1,
|
||||
animation: {
|
||||
duration: 0
|
||||
},
|
||||
scales: {
|
||||
yAxes: [{
|
||||
id: 'time',
|
||||
position: 'right',
|
||||
ticks: {
|
||||
beginAtZero: false,
|
||||
padding: 10,
|
||||
callback: function(value, index, values) {
|
||||
return (1000 * value).toFixed(2) + "ms";
|
||||
}
|
||||
}
|
||||
}],
|
||||
xAxes: [{
|
||||
ticks: {
|
||||
minRotation: 0,
|
||||
maxRotation: 0,
|
||||
maxTicksLimit: 20
|
||||
}
|
||||
}]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Vue.component('delta-time-graph', {
|
||||
props: ['tick', 'data'],
|
||||
updated() {
|
||||
this.updateChart();
|
||||
},
|
||||
data: function() {
|
||||
return {
|
||||
chart: undefined
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
setValues() {
|
||||
if (!this.data.world) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.chart) {
|
||||
this.createChart();
|
||||
}
|
||||
|
||||
var labels = [];
|
||||
var length = this.data.world.history_1m.frame_time_total.avg.length;
|
||||
for (var i = 0; i < length; i ++) {
|
||||
labels.push((length - i) + "s");
|
||||
}
|
||||
|
||||
delta_time_chart.data.labels = labels;
|
||||
delta_time_chart.data.datasets[0].data = this.data.world.history_1m.delta_time.avg;
|
||||
delta_time_chart.data.datasets[1].data = this.data.world.history_1m.delta_time.min;
|
||||
delta_time_chart.data.datasets[2].data = this.data.world.history_1m.delta_time.max;
|
||||
},
|
||||
createChart() {
|
||||
const ctx = document.getElementById('delta-time-graph');
|
||||
this.chart = new Chart(ctx, {
|
||||
type: delta_time_chart.type,
|
||||
data: delta_time_chart.data,
|
||||
options: delta_time_chart.options
|
||||
});
|
||||
},
|
||||
updateChart() {
|
||||
if (!this.data.world) {
|
||||
return;
|
||||
}
|
||||
this.setValues();
|
||||
this.chart.update();
|
||||
}
|
||||
},
|
||||
template: `
|
||||
<div class="app-graph">
|
||||
<canvas id="delta-time-graph" :data-fps="tick"></canvas>
|
||||
</div>`
|
||||
});
|
|
@ -0,0 +1,116 @@
|
|||
|
||||
const entity_chart = {
|
||||
type: 'line',
|
||||
data: {
|
||||
labels: [],
|
||||
datasets: [
|
||||
{
|
||||
label: 'Total',
|
||||
data: [],
|
||||
backgroundColor: [ 'rgba(0,0,0,0)' ],
|
||||
borderColor: [
|
||||
'#5BE595',
|
||||
],
|
||||
borderWidth: 2,
|
||||
pointRadius: 0,
|
||||
yAxisID: "count"
|
||||
},
|
||||
{
|
||||
label: 'Matched',
|
||||
data: [],
|
||||
backgroundColor: [ 'rgba(0,0,0,0)' ],
|
||||
borderColor: [
|
||||
'#46D9E6',
|
||||
],
|
||||
borderWidth: 2,
|
||||
pointRadius: 0,
|
||||
yAxisID: "count"
|
||||
}
|
||||
]
|
||||
},
|
||||
options: {
|
||||
title: {
|
||||
display: false
|
||||
},
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
lineTension: 1,
|
||||
animation: {
|
||||
duration: 0
|
||||
},
|
||||
legend: {
|
||||
display: true
|
||||
},
|
||||
scales: {
|
||||
yAxes: [{
|
||||
id: 'count',
|
||||
ticks: {
|
||||
beginAtZero: true,
|
||||
padding: 10,
|
||||
callback: function(value, index, values) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
}],
|
||||
xAxes: [{
|
||||
ticks: {
|
||||
minRotation: 0,
|
||||
maxRotation: 0,
|
||||
maxTicksLimit: 20
|
||||
}
|
||||
}]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Vue.component('entity-graph', {
|
||||
props: ['tick', 'data'],
|
||||
updated() {
|
||||
this.updateChart();
|
||||
},
|
||||
data: function() {
|
||||
return {
|
||||
chart: undefined
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
setValues() {
|
||||
if (!this.data.world) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.chart) {
|
||||
this.createChart();
|
||||
}
|
||||
|
||||
var labels = [];
|
||||
var length = this.data.world.history_1m.frame_time_total.avg.length;
|
||||
for (var i = 0; i < length; i ++) {
|
||||
labels.push((length - i) + "s");
|
||||
}
|
||||
|
||||
entity_chart.data.labels = labels;
|
||||
entity_chart.data.datasets[0].data = this.data.world.history_1m.entity_count.avg;
|
||||
entity_chart.data.datasets[1].data = this.data.world.history_1m.matched_entity_count.avg;
|
||||
},
|
||||
createChart() {
|
||||
const ctx = document.getElementById('entity-graph');
|
||||
this.chart = new Chart(ctx, {
|
||||
type: entity_chart.type,
|
||||
data: entity_chart.data,
|
||||
options: entity_chart.options
|
||||
});
|
||||
},
|
||||
updateChart() {
|
||||
if (!this.data.world) {
|
||||
return;
|
||||
}
|
||||
this.setValues();
|
||||
this.chart.update();
|
||||
}
|
||||
},
|
||||
template: `
|
||||
<div class="app-graph">
|
||||
<canvas id="entity-graph" :data-fps="tick"></canvas>
|
||||
</div>`
|
||||
});
|
|
@ -0,0 +1,149 @@
|
|||
const operation_chart = {
|
||||
type: 'line',
|
||||
data: {
|
||||
labels: [],
|
||||
datasets: [
|
||||
{
|
||||
label: 'New',
|
||||
data: [],
|
||||
backgroundColor: [ 'rgba(0,0,0,0)' ],
|
||||
borderColor: [
|
||||
'#5BE595',
|
||||
],
|
||||
borderWidth: 2,
|
||||
pointRadius: 0,
|
||||
yAxisID: "count"
|
||||
},
|
||||
{
|
||||
label: 'Delete',
|
||||
data: [],
|
||||
backgroundColor: [ 'rgba(0,0,0,0)' ],
|
||||
borderColor: [
|
||||
'#46D9E6',
|
||||
],
|
||||
borderWidth: 2,
|
||||
pointRadius: 0,
|
||||
yAxisID: "count"
|
||||
},
|
||||
{
|
||||
label: 'Set',
|
||||
data: [],
|
||||
backgroundColor: [ 'rgba(0,0,0,0)' ],
|
||||
borderColor: [
|
||||
'#2D5BE6',
|
||||
],
|
||||
borderWidth: 2,
|
||||
pointRadius: 0,
|
||||
yAxisID: "count"
|
||||
},
|
||||
{
|
||||
label: 'Add',
|
||||
data: [],
|
||||
backgroundColor: [ 'rgba(0,0,0,0)' ],
|
||||
borderColor: [
|
||||
'#6146E6',
|
||||
],
|
||||
borderWidth: 2,
|
||||
pointRadius: 0,
|
||||
yAxisID: "count"
|
||||
},
|
||||
{
|
||||
label: 'Remove',
|
||||
data: [],
|
||||
backgroundColor: [ 'rgba(0,0,0,0)' ],
|
||||
borderColor: [
|
||||
'#E550E6',
|
||||
],
|
||||
borderWidth: 2,
|
||||
pointRadius: 0,
|
||||
yAxisID: "count"
|
||||
}
|
||||
]
|
||||
},
|
||||
options: {
|
||||
title: {
|
||||
display: false
|
||||
},
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
lineTension: 1,
|
||||
animation: {
|
||||
duration: 0
|
||||
},
|
||||
scales: {
|
||||
yAxes: [{
|
||||
id: 'count',
|
||||
ticks: {
|
||||
beginAtZero: true,
|
||||
padding: 10,
|
||||
callback: function(value, index, values) {
|
||||
return value + "/s";
|
||||
}
|
||||
}
|
||||
}],
|
||||
xAxes: [{
|
||||
ticks: {
|
||||
minRotation: 0,
|
||||
maxRotation: 0,
|
||||
maxTicksLimit: 20
|
||||
}
|
||||
}]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Vue.component('operation-graph', {
|
||||
props: ['tick', 'data'],
|
||||
updated() {
|
||||
this.updateChart();
|
||||
},
|
||||
data: function() {
|
||||
return {
|
||||
chart: undefined
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
setValues() {
|
||||
if (!this.data.world) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.chart) {
|
||||
this.createChart();
|
||||
}
|
||||
|
||||
var labels = [];
|
||||
var length = this.data.world.history_1m.frame_time_total.avg.length;
|
||||
for (var i = 0; i < length; i ++) {
|
||||
labels.push((length - i) + "s");
|
||||
}
|
||||
|
||||
operation_chart.data.labels = labels;
|
||||
operation_chart.data.datasets[0].data = this.data.world.history_1m.new_count.avg;
|
||||
operation_chart.data.datasets[1].data = this.data.world.history_1m.delete_count.avg;
|
||||
operation_chart.data.datasets[2].data = this.data.world.history_1m.set_count.avg;
|
||||
operation_chart.data.datasets[3].data = this.data.world.history_1m.add_count.avg;
|
||||
operation_chart.data.datasets[4].data = this.data.world.history_1m.remove_count.avg;
|
||||
},
|
||||
createChart() {
|
||||
const ctx = document.getElementById('operation-graph');
|
||||
|
||||
this.chart = new Chart(ctx, {
|
||||
type: operation_chart.type,
|
||||
data: operation_chart.data,
|
||||
options: operation_chart.options
|
||||
});
|
||||
},
|
||||
updateChart() {
|
||||
if (!this.data.world) {
|
||||
return;
|
||||
}
|
||||
this.setValues();
|
||||
this.chart.update();
|
||||
}
|
||||
},
|
||||
template: `
|
||||
<div class="app-graph">
|
||||
<canvas id="operation-graph" :data-fps="tick"></canvas>
|
||||
</div>`
|
||||
});
|
|
@ -0,0 +1,190 @@
|
|||
|
||||
Vue.component('app-overview', {
|
||||
mounted: function() {
|
||||
this.startRequesting("");
|
||||
},
|
||||
beforeDestroy: function() {
|
||||
this.stopRequesting();
|
||||
},
|
||||
data: function() {
|
||||
return {
|
||||
data: {},
|
||||
data_request: undefined,
|
||||
error: false,
|
||||
tick: 0
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
request_data() {
|
||||
var url = "metrics?world=yes";
|
||||
|
||||
this.tick ++;
|
||||
|
||||
app.get(url, (msg) => {
|
||||
this.data = msg;
|
||||
this.error = false;
|
||||
}, (Http) => {
|
||||
if (Http.status == 404) {
|
||||
this.error = true;
|
||||
}
|
||||
});
|
||||
},
|
||||
// Stop periodically requesting the scope
|
||||
stopRequesting() {
|
||||
this.data = [];
|
||||
clearInterval(this.data_request);
|
||||
},
|
||||
// Start periodically requesting a scope
|
||||
startRequesting(scope) {
|
||||
this.stopRequesting();
|
||||
|
||||
// Initial request
|
||||
this.request_data();
|
||||
|
||||
// Start periodic request
|
||||
this.data_request = window.setInterval(function() {
|
||||
this.request_data();
|
||||
}.bind(this), 1000);
|
||||
},
|
||||
entity_count() {
|
||||
if (!this.data.world) {
|
||||
return 0;
|
||||
}
|
||||
return this.data.world.current.entity_count;
|
||||
},
|
||||
operation_count() {
|
||||
if (!this.data.world) {
|
||||
return 0;
|
||||
}
|
||||
return (this.data.world.current.add_count +
|
||||
this.data.world.current.remove_count +
|
||||
this.data.world.current.clear_count +
|
||||
this.data.world.current.delete_count +
|
||||
this.data.world.current.set_count +
|
||||
this.data.world.current.bulk_new_count).toFixed(0);
|
||||
},
|
||||
system_count() {
|
||||
if (!this.data.world) {
|
||||
return 0;
|
||||
}
|
||||
return (this.data.world.current.systems_ran).toFixed(0);
|
||||
},
|
||||
component_count() {
|
||||
if (!this.data.world) {
|
||||
return 0;
|
||||
}
|
||||
return this.data.world.current.component_count;
|
||||
},
|
||||
table_count() {
|
||||
if (!this.data.world) {
|
||||
return 0;
|
||||
}
|
||||
return this.data.world.current.table_count;
|
||||
},
|
||||
fragmentation() {
|
||||
if (!this.data.world) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Compute global fragmentation as total matched tables divided by total
|
||||
* matched entities. Subtract one from the tables, so that if there is a
|
||||
* single entity matching a single table, fragmentation is 0% */
|
||||
return (100 * (this.data.world.current.matched_table_count - 1) /
|
||||
this.data.world.current.matched_entity_count).toFixed(0);
|
||||
}
|
||||
},
|
||||
template: `
|
||||
<div class="overview">
|
||||
<div class="metric-row">
|
||||
|
||||
<div class="metric metric-column">
|
||||
<div class="metric-header">entities</div>
|
||||
<div class="metric-content">
|
||||
{{entity_count()}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="metric metric-column">
|
||||
<div class="metric-header">components</div>
|
||||
<div class="metric-content">
|
||||
{{component_count()}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="metric metric-column">
|
||||
<div class="metric-header">systems</div>
|
||||
<div class="metric-content">
|
||||
{{system_count()}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="metric metric-column">
|
||||
<div class="metric-header">tables</div>
|
||||
<div class="metric-content">
|
||||
{{table_count()}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="metric metric-column">
|
||||
<div class="metric-header">fragmentation</div>
|
||||
<div class="metric-content">
|
||||
{{fragmentation()}}%
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="metric">
|
||||
<div class="metric-header">performance</div>
|
||||
<div class="metric-content">
|
||||
<performance-graph :data="data" :tick="tick"></performance-graph>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="metric">
|
||||
<div class="metric-header">delta time</div>
|
||||
<div class="metric-content">
|
||||
<delta-time-graph :data="data" :tick="tick"></delta-time-graph>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="metric">
|
||||
<div class="metric-header">operations ({{operation_count()}})</div>
|
||||
<div class="metric-content">
|
||||
<operation-graph :data="data" :tick="tick"></operation-graph>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="metric">
|
||||
<div class="metric-header">entities ({{entity_count()}})</div>
|
||||
<div class="metric-content">
|
||||
<entity-graph :data="data" :tick="tick"></entity-graph>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="metric">
|
||||
<div class="metric-header">tables ({{table_count()}})</div>
|
||||
<div class="metric-content">
|
||||
<table-graph :data="data" :tick="tick"></table-graph>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
});
|
||||
|
||||
app.app_loaded("overview", [{
|
||||
name: "entity-graph",
|
||||
url: "apps/overview/entity_graph.js"
|
||||
}, {
|
||||
name: "performance-graph",
|
||||
url: "apps/overview/performance_graph.js"
|
||||
}, {
|
||||
name: "operation-graph",
|
||||
url: "apps/overview/operation_graph.js"
|
||||
}, {
|
||||
name: "table-graph",
|
||||
url: "apps/overview/table_graph.js"
|
||||
}, {
|
||||
name: "delta-time-graph",
|
||||
url: "apps/overview/delta_time_graph.js"
|
||||
}]);
|
|
@ -0,0 +1,149 @@
|
|||
const performance_chart = {
|
||||
type: 'line',
|
||||
data: {
|
||||
labels: [],
|
||||
datasets: [
|
||||
{
|
||||
label: 'FPS',
|
||||
data: [],
|
||||
backgroundColor: [ 'rgba(0,0,0,0)' ],
|
||||
borderColor: [
|
||||
'#5BE595',
|
||||
],
|
||||
borderWidth: 2,
|
||||
pointRadius: 0,
|
||||
yAxisID: "fps"
|
||||
},
|
||||
{
|
||||
label: 'Frame',
|
||||
data: [],
|
||||
backgroundColor: [ 'rgba(0,0,0,0)' ],
|
||||
borderColor: [
|
||||
'#6146E6',
|
||||
],
|
||||
borderWidth: 2,
|
||||
pointRadius: 0,
|
||||
yAxisID: "time"
|
||||
},
|
||||
{
|
||||
label: 'Systems',
|
||||
data: [],
|
||||
backgroundColor: [ 'rgba(0,0,0,0)' ],
|
||||
borderColor: [
|
||||
'#46D9E6',
|
||||
],
|
||||
borderWidth: 2,
|
||||
pointRadius: 0,
|
||||
yAxisID: "time"
|
||||
},
|
||||
{
|
||||
label: 'Merging',
|
||||
data: [],
|
||||
backgroundColor: [ 'rgba(0,0,0,0)' ],
|
||||
borderColor: [
|
||||
'#E550E6',
|
||||
],
|
||||
borderWidth: 2,
|
||||
pointRadius: 0,
|
||||
yAxisID: "time"
|
||||
}
|
||||
]
|
||||
},
|
||||
options: {
|
||||
title: {
|
||||
display: false
|
||||
},
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
lineTension: 1,
|
||||
animation: {
|
||||
duration: 0
|
||||
},
|
||||
scales: {
|
||||
yAxes: [{
|
||||
id: 'fps',
|
||||
ticks: {
|
||||
beginAtZero: true,
|
||||
padding: 10,
|
||||
callback: function(value, index, values) {
|
||||
return value + "Hz";
|
||||
}
|
||||
}
|
||||
}, {
|
||||
id: 'time',
|
||||
position: 'right',
|
||||
ticks: {
|
||||
beginAtZero: true,
|
||||
padding: 10,
|
||||
callback: function(value, index, values) {
|
||||
return (1000 * value).toFixed(2) + "ms";
|
||||
}
|
||||
}
|
||||
}],
|
||||
xAxes: [{
|
||||
ticks: {
|
||||
minRotation: 0,
|
||||
maxRotation: 0,
|
||||
maxTicksLimit: 20
|
||||
}
|
||||
}]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Vue.component('performance-graph', {
|
||||
props: ['tick', 'data'],
|
||||
updated() {
|
||||
this.updateChart();
|
||||
},
|
||||
data: function() {
|
||||
return {
|
||||
chart: undefined
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
setValues() {
|
||||
if (!this.data.world) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.chart) {
|
||||
this.createChart();
|
||||
}
|
||||
|
||||
var labels = [];
|
||||
var length = this.data.world.history_1m.frame_time_total.avg.length;
|
||||
for (var i = 0; i < length; i ++) {
|
||||
labels.push((length - i) + "s");
|
||||
}
|
||||
|
||||
performance_chart.data.labels = labels;
|
||||
performance_chart.data.datasets[0].data = this.data.world.history_1m.fps.avg;
|
||||
performance_chart.data.datasets[1].data = this.data.world.history_1m.frame_time_total.avg;
|
||||
performance_chart.data.datasets[2].data = this.data.world.history_1m.system_time_total.avg;
|
||||
performance_chart.data.datasets[3].data = this.data.world.history_1m.merge_time_total.avg;
|
||||
},
|
||||
createChart() {
|
||||
const ctx = document.getElementById('fps-graph');
|
||||
|
||||
performance_chart.options.scales.yAxes[1].ticks.suggestedMax = 1.0 / this.data.target_fps;
|
||||
|
||||
this.chart = new Chart(ctx, {
|
||||
type: performance_chart.type,
|
||||
data: performance_chart.data,
|
||||
options: performance_chart.options
|
||||
});
|
||||
},
|
||||
updateChart() {
|
||||
if (!this.data.world) {
|
||||
return;
|
||||
}
|
||||
this.setValues();
|
||||
this.chart.update();
|
||||
}
|
||||
},
|
||||
template: `
|
||||
<div class="app-graph">
|
||||
<canvas id="fps-graph" :data-fps="tick"></canvas>
|
||||
</div>`
|
||||
});
|
|
@ -0,0 +1,140 @@
|
|||
|
||||
const table_chart = {
|
||||
type: 'line',
|
||||
data: {
|
||||
labels: [],
|
||||
datasets: [
|
||||
{
|
||||
label: 'Total',
|
||||
data: [],
|
||||
backgroundColor: [ 'rgba(0,0,0,0)' ],
|
||||
borderColor: [
|
||||
'#5BE595',
|
||||
],
|
||||
borderWidth: 2,
|
||||
pointRadius: 0,
|
||||
yAxisID: "count"
|
||||
},
|
||||
{
|
||||
label: 'Matched',
|
||||
data: [],
|
||||
backgroundColor: [ 'rgba(0,0,0,0)' ],
|
||||
borderColor: [
|
||||
'#46D9E6',
|
||||
],
|
||||
borderWidth: 2,
|
||||
pointRadius: 0,
|
||||
yAxisID: "count"
|
||||
},
|
||||
{
|
||||
label: 'Singleton',
|
||||
data: [],
|
||||
backgroundColor: [ 'rgba(0,0,0,0)' ],
|
||||
borderColor: [
|
||||
'#4596E5',
|
||||
],
|
||||
borderWidth: 2,
|
||||
pointRadius: 0,
|
||||
yAxisID: "count"
|
||||
},
|
||||
{
|
||||
label: 'Empty',
|
||||
data: [],
|
||||
backgroundColor: [ 'rgba(0,0,0,0)' ],
|
||||
borderColor: [
|
||||
'#2D5BE6',
|
||||
],
|
||||
borderWidth: 2,
|
||||
pointRadius: 0,
|
||||
yAxisID: "count"
|
||||
}
|
||||
]
|
||||
},
|
||||
options: {
|
||||
title: {
|
||||
display: false
|
||||
},
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
lineTension: 1,
|
||||
animation: {
|
||||
duration: 0
|
||||
},
|
||||
legend: {
|
||||
display: true
|
||||
},
|
||||
scales: {
|
||||
yAxes: [{
|
||||
id: 'count',
|
||||
ticks: {
|
||||
beginAtZero: true,
|
||||
padding: 10,
|
||||
callback: function(value, index, values) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
}],
|
||||
xAxes: [{
|
||||
ticks: {
|
||||
minRotation: 0,
|
||||
maxRotation: 0,
|
||||
maxTicksLimit: 20
|
||||
}
|
||||
}]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Vue.component('table-graph', {
|
||||
props: ['tick', 'data'],
|
||||
updated() {
|
||||
this.updateChart();
|
||||
},
|
||||
data: function() {
|
||||
return {
|
||||
chart: undefined
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
setValues() {
|
||||
if (!this.data.world) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.chart) {
|
||||
this.createChart();
|
||||
}
|
||||
|
||||
var labels = [];
|
||||
var length = this.data.world.history_1m.table_count.avg.length;
|
||||
for (var i = 0; i < length; i ++) {
|
||||
labels.push((length - i) + "s");
|
||||
}
|
||||
|
||||
table_chart.data.labels = labels;
|
||||
table_chart.data.datasets[0].data = this.data.world.history_1m.table_count.avg;
|
||||
table_chart.data.datasets[1].data = this.data.world.history_1m.matched_table_count.avg;
|
||||
table_chart.data.datasets[2].data = this.data.world.history_1m.singleton_table_count.avg;
|
||||
table_chart.data.datasets[3].data = this.data.world.history_1m.empty_table_count.avg;
|
||||
},
|
||||
createChart() {
|
||||
const ctx = document.getElementById('table-graph');
|
||||
this.chart = new Chart(ctx, {
|
||||
type: table_chart.type,
|
||||
data: table_chart.data,
|
||||
options: table_chart.options
|
||||
});
|
||||
},
|
||||
updateChart() {
|
||||
if (!this.data.world) {
|
||||
return;
|
||||
}
|
||||
this.setValues();
|
||||
this.chart.update();
|
||||
}
|
||||
},
|
||||
template: `
|
||||
<div class="app-graph">
|
||||
<canvas id="table-graph" :data-fps="tick"></canvas>
|
||||
</div>`
|
||||
});
|
|
@ -0,0 +1,187 @@
|
|||
|
||||
div.system-header {
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
|
||||
div.systems {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
div.system {
|
||||
position: relative;
|
||||
background-color: #1E2022;
|
||||
color: #eee;
|
||||
font-size: 16px;
|
||||
padding: 10px;
|
||||
padding-top: 10px;
|
||||
padding-bottom: 5px;
|
||||
margin-left: 10px;
|
||||
margin-right: 10px;
|
||||
margin-top: 5px;
|
||||
height: 20px;
|
||||
border-left-style: solid;
|
||||
border-left-width: 5px;
|
||||
border-left-color: #181a1b;
|
||||
transition: border-left-color 0.3s;
|
||||
}
|
||||
|
||||
div.system-query {
|
||||
position: relative;
|
||||
background-color: #26292b;
|
||||
color: #eee;
|
||||
font-size: 16px;
|
||||
padding: 10px;
|
||||
padding-top: 5px;
|
||||
margin-left: 10px;
|
||||
margin-right: 10px;
|
||||
margin-top: 0px;
|
||||
height: 20px;
|
||||
border-left-style: solid;
|
||||
border-left-width: 5px;
|
||||
border-left-color: #181a1b;
|
||||
transition: border-left-color 0.3s;
|
||||
}
|
||||
|
||||
span.system-module {
|
||||
color: #88949b;
|
||||
font-size: 14px;
|
||||
font-weight: normal;
|
||||
padding-left: 5px;
|
||||
}
|
||||
|
||||
span.system-metrics {
|
||||
position: absolute;
|
||||
top: 6px;
|
||||
right: 380px;
|
||||
background-color: #26292b;
|
||||
color: #88949b;
|
||||
font-size: 14px;
|
||||
font-weight: normal;
|
||||
border-radius: 2px;
|
||||
margin-left: 5px;
|
||||
padding: 3px;
|
||||
padding-left: 5px;
|
||||
padding-right: 5px;
|
||||
}
|
||||
|
||||
div.merge {
|
||||
position: relative;
|
||||
background-color: #1E2022;
|
||||
color: #eee;
|
||||
font-size: 16px;
|
||||
padding: 10px;
|
||||
margin-left: 10px;
|
||||
margin-right: 10px;
|
||||
margin-top: 5px;
|
||||
border-left-color: #181818;
|
||||
border-left-style: solid;
|
||||
border-left-width: 5px;
|
||||
}
|
||||
|
||||
div.system-entity-graph {
|
||||
position: absolute;
|
||||
top: 7px;
|
||||
right: 35px;
|
||||
height: 28px;
|
||||
width: 300px;
|
||||
background-color: #26292b;
|
||||
}
|
||||
|
||||
div.system-time-graph {
|
||||
position: absolute;
|
||||
top: 7px;
|
||||
right: 35px;
|
||||
height: 28px;
|
||||
width: 300px
|
||||
}
|
||||
|
||||
div.system-graph-label {
|
||||
position: absolute;
|
||||
top: -5px;
|
||||
left: -65px;
|
||||
text-align: right;
|
||||
padding-top: 8px;
|
||||
padding-bottom: 8px;
|
||||
padding-right: 5px;
|
||||
width: 60px;
|
||||
font-size: 13px;
|
||||
color: #888;
|
||||
}
|
||||
|
||||
div.system-graph-label-filled {
|
||||
background-color: #26292b;
|
||||
}
|
||||
|
||||
div.system-graph-label-secondary {
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
left: 295px;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
span.system-component {
|
||||
position: relative;
|
||||
top: 5px;
|
||||
background-color: #1E2022;
|
||||
padding: 4px;
|
||||
color: #4fa7ff;
|
||||
margin-right: 5px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
span.system-component-annotation {
|
||||
position: relative;
|
||||
left: -5px;
|
||||
background-color: #181a1b;
|
||||
padding: 4px;
|
||||
padding-left: 3px;
|
||||
padding-right: 0px;
|
||||
color: #47B576;
|
||||
margin-right: 0px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
span.system-component-not {
|
||||
color: #B5494B;
|
||||
}
|
||||
|
||||
span.system-component-tag {
|
||||
color: #9546E5;
|
||||
}
|
||||
|
||||
div.pct-lowest {
|
||||
color: #888;
|
||||
}
|
||||
|
||||
div.pct-low {
|
||||
color: #5BE595;
|
||||
}
|
||||
|
||||
div.pct-medium {
|
||||
color: #C6CA50;
|
||||
}
|
||||
|
||||
div.pct-high {
|
||||
color: #B56D48;
|
||||
}
|
||||
|
||||
div.pct-highest {
|
||||
color: #B5494B;
|
||||
}
|
||||
|
||||
|
||||
div.system-low {
|
||||
border-left-color: #5BE595;
|
||||
}
|
||||
|
||||
div.system-medium {
|
||||
border-left-color: #C6CA50;
|
||||
}
|
||||
|
||||
div.system-high {
|
||||
border-left-color: #B56D48;
|
||||
}
|
||||
|
||||
div.system-highest {
|
||||
border-left-color: #B5494B;
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
|
||||
|
||||
Vue.component('system', {
|
||||
props: ['tick', 'system', 'system_time'],
|
||||
methods: {
|
||||
entity_count() {
|
||||
return this.system.current.matched_entity_count;
|
||||
},
|
||||
table_count() {
|
||||
return this.system.current.matched_table_count;
|
||||
},
|
||||
fragmentation() {
|
||||
return (((this.table_count() - 1) / this.entity_count()) * 100).toFixed(1) + "%";
|
||||
},
|
||||
percentage() {
|
||||
return (this.system.current.time_spent / this.system_time) * 100;
|
||||
},
|
||||
percentage_class() {
|
||||
let pct = this.percentage();
|
||||
if (pct < 1.0) {
|
||||
return "system-lowest";
|
||||
} else if (pct < 5.0) {
|
||||
return "system-low";
|
||||
} else if (pct < 10.0) {
|
||||
return "system-medium";
|
||||
} else if (pct < 20.0) {
|
||||
return "system-high";
|
||||
} else {
|
||||
return "system-highest";
|
||||
}
|
||||
}
|
||||
},
|
||||
template: `
|
||||
<div>
|
||||
<div :class="'system ' + percentage_class()">
|
||||
<div class="system-header">
|
||||
{{system.name}}
|
||||
<span class="system-module" v-if="system.module">{{system.module}}</span>
|
||||
<span class="system-metrics" v-if="entity_count()">T:{{table_count()}} - F:{{fragmentation()}}</span>
|
||||
<system-time-graph :system="system" :system_time="system_time" :percentage="percentage()" :tick="tick"></system-time-graph>
|
||||
</div>
|
||||
</div>
|
||||
<div :class="'system-query ' + percentage_class()">
|
||||
<system-component v-for="(component, i) in system.signature" :component="component" :key="i"></system-component>
|
||||
<system-entity-graph :system="system" :tick="tick"></system-entity-graph>
|
||||
</div>
|
||||
</div>`
|
||||
});
|
|
@ -0,0 +1,31 @@
|
|||
|
||||
Vue.component('system-component', {
|
||||
props: ['component'],
|
||||
methods: {
|
||||
css() {
|
||||
if (this.component.exclude) {
|
||||
return "system-component-not";
|
||||
}
|
||||
return "";
|
||||
}
|
||||
},
|
||||
template: `
|
||||
<span :class="'system-component ' + css()">
|
||||
<span class="system-component-annotation" v-if="component.const">
|
||||
in
|
||||
</span>
|
||||
<span class="system-component-annotation" v-if="component.singleton">
|
||||
$
|
||||
</span>
|
||||
<span class="system-component-annotation" v-else-if="component.parent">
|
||||
parent
|
||||
</span>
|
||||
<span class="system-component-annotation" v-else-if="component.ref">
|
||||
ref
|
||||
</span>
|
||||
<span class="system-component-annotation" v-else-if="component.empty">
|
||||
0
|
||||
</span>
|
||||
{{component.name}}
|
||||
</span>`
|
||||
});
|
|
@ -0,0 +1,125 @@
|
|||
|
||||
const system_entity_chart = {
|
||||
type: 'line',
|
||||
data: {
|
||||
labels: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
|
||||
11, 12, 13, 14, 15, 16, 17, 18, 19, 20,
|
||||
21, 22, 23, 24, 12, 26, 27, 28, 29, 30,
|
||||
31, 32, 33, 34, 13, 36, 37, 38, 39, 40,
|
||||
41, 42, 43, 44, 14, 46, 47, 48, 49, 50,
|
||||
51, 52, 53, 54, 15, 56, 57, 58, 59, 60],
|
||||
datasets: [
|
||||
{
|
||||
label: 'Matched entities',
|
||||
data: [],
|
||||
backgroundColor: [ '#26352c' ],
|
||||
borderColor: [ '#5BE595', ],
|
||||
borderWidth: 2,
|
||||
pointRadius: 0,
|
||||
fill: true,
|
||||
yAxisID: "entities"
|
||||
}
|
||||
]
|
||||
},
|
||||
options: {
|
||||
title: {
|
||||
display: false
|
||||
},
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
lineTension: 1,
|
||||
animation: {
|
||||
duration: 0
|
||||
},
|
||||
hover: {
|
||||
animationDuration: 0 // duration of animations when hovering an item
|
||||
},
|
||||
responsiveAnimationDuration: 0,
|
||||
legend: {
|
||||
display: false
|
||||
},
|
||||
elements: {
|
||||
line: {
|
||||
tension: 0 // disables bezier curves
|
||||
}
|
||||
},
|
||||
scales: {
|
||||
yAxes: [{
|
||||
id: 'entities',
|
||||
position: 'right',
|
||||
gridLines: {
|
||||
display:false
|
||||
},
|
||||
ticks: {
|
||||
display: false,
|
||||
beginAtZero: true,
|
||||
maxTicksLimit: 2
|
||||
}
|
||||
}],
|
||||
xAxes: [{
|
||||
gridLines: {
|
||||
display:false
|
||||
},
|
||||
ticks: {
|
||||
display: false
|
||||
}
|
||||
}]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Vue.component('system-entity-graph', {
|
||||
props: ['tick', 'system'],
|
||||
updated() {
|
||||
this.updateChart();
|
||||
},
|
||||
data: function() {
|
||||
return {
|
||||
chart: undefined
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
setValues() {
|
||||
if (!this.system.history_1m) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.chart) {
|
||||
this.createChart();
|
||||
}
|
||||
|
||||
system_entity_chart.data.datasets[0].data = this.system.history_1m.matched_entity_count.avg;
|
||||
},
|
||||
|
||||
createChart() {
|
||||
const ctx = document.getElementById('system-entity-' + this.system.name + '-graph');
|
||||
this.chart = new Chart(ctx, {
|
||||
type: system_entity_chart.type,
|
||||
data: system_entity_chart.data,
|
||||
options: system_entity_chart.options
|
||||
});
|
||||
},
|
||||
|
||||
updateChart() {
|
||||
if (!this.system.history_1m) {
|
||||
return;
|
||||
}
|
||||
this.setValues();
|
||||
this.chart.update();
|
||||
},
|
||||
|
||||
entity_count() {
|
||||
if (!this.system || !this.system.current) {
|
||||
return;
|
||||
}
|
||||
return this.system.current.matched_entity_count;
|
||||
}
|
||||
},
|
||||
template: `
|
||||
<div class="system-entity-graph">
|
||||
<div class="system-graph-label system-graph-label-filled">
|
||||
{{entity_count()}}
|
||||
</div>
|
||||
<canvas :id="'system-entity-' + system.name + '-graph'" :data-fps="tick"></canvas>
|
||||
</div>`
|
||||
});
|
|
@ -0,0 +1,170 @@
|
|||
|
||||
const system_time_chart = {
|
||||
type: 'line',
|
||||
data: {
|
||||
labels: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
|
||||
11, 12, 13, 14, 15, 16, 17, 18, 19, 20,
|
||||
21, 22, 23, 24, 12, 26, 27, 28, 29, 30,
|
||||
31, 32, 33, 34, 13, 36, 37, 38, 39, 40,
|
||||
41, 42, 43, 44, 14, 46, 47, 48, 49, 50,
|
||||
51, 52, 53, 54, 15, 56, 57, 58, 59, 60],
|
||||
datasets: [
|
||||
{
|
||||
label: 'Matched entities',
|
||||
data: [],
|
||||
backgroundColor: [ '#26537F' ],
|
||||
borderColor: [
|
||||
'#4596E5',
|
||||
],
|
||||
borderWidth: 2,
|
||||
pointRadius: 0,
|
||||
yAxisID: "entities"
|
||||
}
|
||||
]
|
||||
},
|
||||
options: {
|
||||
title: {
|
||||
display: false
|
||||
},
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
lineTension: 1,
|
||||
animation: {
|
||||
duration: 0
|
||||
},
|
||||
hover: {
|
||||
animationDuration: 0 // duration of animations when hovering an item
|
||||
},
|
||||
responsiveAnimationDuration: 0,
|
||||
legend: {
|
||||
display: false
|
||||
},
|
||||
elements: {
|
||||
line: {
|
||||
tension: 0 // disables bezier curves
|
||||
}
|
||||
},
|
||||
scales: {
|
||||
yAxes: [{
|
||||
id: 'entities',
|
||||
position: 'right',
|
||||
gridLines: {
|
||||
display:false
|
||||
},
|
||||
ticks: {
|
||||
display: false,
|
||||
beginAtZero: true,
|
||||
maxTicksLimit: 2
|
||||
}
|
||||
}],
|
||||
xAxes: [{
|
||||
gridLines: {
|
||||
display:false
|
||||
},
|
||||
ticks: {
|
||||
display: false
|
||||
}
|
||||
}]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Vue.component('system-time-graph', {
|
||||
props: ['tick', 'system', 'system_time', 'percentage'],
|
||||
updated() {
|
||||
this.updateChart();
|
||||
},
|
||||
data: function() {
|
||||
return {
|
||||
chart: undefined
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
setValues() {
|
||||
if (!this.system.history_1m) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.chart) {
|
||||
this.createChart();
|
||||
}
|
||||
|
||||
system_time_chart.data.datasets[0].data = this.system.history_1m.time_spent.avg;
|
||||
},
|
||||
|
||||
createChart() {
|
||||
const ctx = document.getElementById('system-time-' + this.system.name + '-graph');
|
||||
this.chart = new Chart(ctx, {
|
||||
type: system_time_chart.type,
|
||||
data: system_time_chart.data,
|
||||
options: system_time_chart.options
|
||||
});
|
||||
},
|
||||
|
||||
updateChart() {
|
||||
if (!this.system.history_1m) {
|
||||
return;
|
||||
}
|
||||
this.setValues();
|
||||
this.chart.update();
|
||||
},
|
||||
|
||||
time_spent() {
|
||||
let time_spent = this.system.current.time_spent;
|
||||
if (time_spent >= 1.0) {
|
||||
return time_spent.toFixed(0) + "s";
|
||||
}
|
||||
|
||||
time_spent *= 1000;
|
||||
if (time_spent >= 1.0) {
|
||||
return time_spent.toFixed(0) + "ms";
|
||||
}
|
||||
|
||||
time_spent *= 1000;
|
||||
if (time_spent >= 1.0) {
|
||||
return time_spent.toFixed(0) + "us";
|
||||
}
|
||||
|
||||
time_spent *= 1000;
|
||||
if (time_spent >= 1.0) {
|
||||
return time_spent.toFixed(0) + "ns";
|
||||
} else if (time_spent >= 0.01) {
|
||||
return time_spent.toFixed(2) + "ns";
|
||||
} else {
|
||||
return "0ns";
|
||||
}
|
||||
},
|
||||
percentage_formatted() {
|
||||
let pct = this.percentage;
|
||||
if (pct > 10.0) {
|
||||
return pct.toFixed(0) + "%"
|
||||
} else {
|
||||
return pct.toFixed(1) + "%"
|
||||
}
|
||||
},
|
||||
percentage_class() {
|
||||
let pct = this.percentage;
|
||||
if (pct < 1.0) {
|
||||
return "pct-lowest";
|
||||
} else if (pct < 5.0) {
|
||||
return "pct-low";
|
||||
} else if (pct < 10.0) {
|
||||
return "pct-medium";
|
||||
} else if (pct < 20.0) {
|
||||
return "pct-high";
|
||||
} else {
|
||||
return "pct-highest";
|
||||
}
|
||||
}
|
||||
},
|
||||
template: `
|
||||
<div class="system-time-graph">
|
||||
<div class="system-graph-label">
|
||||
{{time_spent()}}
|
||||
</div>
|
||||
<div :class="'system-graph-label-secondary ' + percentage_class()">
|
||||
{{percentage_formatted()}}
|
||||
</div>
|
||||
<canvas :id="'system-time-' + system.name + '-graph'" :data-fps="tick"></canvas>
|
||||
</div>`
|
||||
});
|
|
@ -0,0 +1,173 @@
|
|||
|
||||
Vue.component('app-systems', {
|
||||
mounted: function() {
|
||||
this.startRequesting("");
|
||||
},
|
||||
beforeDestroy: function() {
|
||||
this.stopRequesting();
|
||||
},
|
||||
data: function() {
|
||||
return {
|
||||
data: {},
|
||||
data_request: undefined,
|
||||
error: false,
|
||||
tick: 0
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
request_data() {
|
||||
var url = "metrics?pipeline=yes";
|
||||
|
||||
this.tick ++;
|
||||
|
||||
app.get(url, (msg) => {
|
||||
this.data = msg;
|
||||
this.error = false;
|
||||
}, (Http) => {
|
||||
if (Http.status == 404) {
|
||||
this.error = true;
|
||||
}
|
||||
});
|
||||
},
|
||||
// Stop periodically requesting the scope
|
||||
stopRequesting() {
|
||||
this.data = [];
|
||||
clearInterval(this.data_request);
|
||||
},
|
||||
// Start periodically requesting a scope
|
||||
startRequesting(scope) {
|
||||
this.stopRequesting();
|
||||
|
||||
// Initial request
|
||||
this.request_data();
|
||||
|
||||
// Start periodic request
|
||||
this.data_request = window.setInterval(function() {
|
||||
this.request_data();
|
||||
}.bind(this), 1000);
|
||||
},
|
||||
format_num(val) {
|
||||
if (val < 1.0) {
|
||||
return val.toFixed(2);
|
||||
} else if (val < 10.0) {
|
||||
return val.toFixed(1);
|
||||
} else {
|
||||
return val.toFixed(0);
|
||||
}
|
||||
},
|
||||
to_unit(val) {
|
||||
if (val >= 1.0) {
|
||||
return this.format_num(val) + "s";
|
||||
}
|
||||
|
||||
val *= 1000;
|
||||
if (val >= 1.0) {
|
||||
return this.format_num(val) + "ms";
|
||||
}
|
||||
|
||||
val *= 1000;
|
||||
if (val >= 1.0) {
|
||||
return this.format_num(val) + "us";
|
||||
}
|
||||
|
||||
val *= 1000;
|
||||
if (val >= 1.0) {
|
||||
return this.format_num(val) + "ns";
|
||||
} else {
|
||||
return this.format_num(val) + "ns";
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
systems() {
|
||||
if (!this.data.pipeline) {
|
||||
return [];
|
||||
}
|
||||
return this.data.pipeline.systems;
|
||||
},
|
||||
fps() {
|
||||
if (!this.data.pipeline) {
|
||||
return 0;
|
||||
}
|
||||
return this.data.pipeline.fps.toFixed(1) + "Hz";
|
||||
},
|
||||
frame_time() {
|
||||
if (!this.data.pipeline) {
|
||||
return 0;
|
||||
}
|
||||
return this.to_unit(this.data.pipeline.frame_time_total);
|
||||
},
|
||||
system_time() {
|
||||
if (!this.data.pipeline) {
|
||||
return 0;
|
||||
}
|
||||
return this.to_unit(this.data.pipeline.system_time_total);
|
||||
},
|
||||
merge_time() {
|
||||
if (!this.data.pipeline) {
|
||||
return 0;
|
||||
}
|
||||
return this.to_unit(this.data.pipeline.merge_time_total);
|
||||
}
|
||||
},
|
||||
template: `
|
||||
<div class="systems">
|
||||
<link rel="stylesheet" href="apps/systems/style.css">
|
||||
|
||||
<div class="metric-row">
|
||||
<div class="metric metric-column">
|
||||
<div class="metric-header">fps</div>
|
||||
<div class="metric-content">
|
||||
{{fps}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="metric metric-column">
|
||||
<div class="metric-header">frame time</div>
|
||||
<div class="metric-content">
|
||||
{{frame_time}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="metric metric-column">
|
||||
<div class="metric-header">system time</div>
|
||||
<div class="metric-content">
|
||||
{{system_time}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="metric metric-column">
|
||||
<div class="metric-header">merge time</div>
|
||||
<div class="metric-content">
|
||||
{{merge_time}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="position: relative;">
|
||||
<div v-for="s in systems">
|
||||
<div v-if="s">
|
||||
<system :system="s" :system_time="data.pipeline.system_time_total" :tick="tick"></system>
|
||||
</div>
|
||||
<div v-else class="merge">
|
||||
Merge
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
});
|
||||
|
||||
app.app_loaded("systems", [{
|
||||
name: "system-entity-graph",
|
||||
url: "apps/systems/system_entity_graph.js"
|
||||
}, {
|
||||
name: "system-time-graph",
|
||||
url: "apps/systems/system_time_graph.js"
|
||||
}, {
|
||||
name: "system",
|
||||
url: "apps/systems/system.js"
|
||||
}, {
|
||||
name: "system-component",
|
||||
url: "apps/systems/system_component.js"
|
||||
}]);
|
|
@ -0,0 +1,253 @@
|
|||
body {
|
||||
font-family: Roboto,Noto Sans,Noto,sans-serif;
|
||||
font-size: 1.0em;
|
||||
font-size: 16px;
|
||||
background-color: #37393E;
|
||||
color: #eee;
|
||||
margin: 0px;
|
||||
}
|
||||
|
||||
#app {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
div.sidebar {
|
||||
position: absolute;
|
||||
background-color: #1E2022;
|
||||
top: 40px;
|
||||
width: 70px;
|
||||
height: calc(100vh - 50px);
|
||||
padding-top: 12px;
|
||||
}
|
||||
|
||||
div.sidebar-button-container {
|
||||
position: relative;
|
||||
height: 45px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
div.sidebar-button {
|
||||
border-radius: 10px;
|
||||
margin-left: 12px;
|
||||
width: 45px;
|
||||
height: 45px;
|
||||
cursor: pointer;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
div.sidebar-selector {
|
||||
position: absolute;
|
||||
background-color: #5BE595;
|
||||
top: 17px;
|
||||
left: -5px;
|
||||
height: 10px;
|
||||
width: 4px;
|
||||
border-radius: 0px 4px 4px 0px;
|
||||
transition: left 100ms, height 150ms, top 150ms;
|
||||
}
|
||||
|
||||
div.sidebar-selector-active {
|
||||
top: 2.3px;
|
||||
height: 40px;
|
||||
left: 0px;
|
||||
}
|
||||
|
||||
div.sidebar-selector-hover {
|
||||
top: 12px;
|
||||
height: 20px;
|
||||
left: 0px;
|
||||
}
|
||||
|
||||
img.sidebar-icon {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
margin-top: 3;
|
||||
}
|
||||
|
||||
div.content {
|
||||
position: absolute;
|
||||
width: calc(100% - 70px);
|
||||
height: calc(100vh - 40px);
|
||||
top: 40px;
|
||||
left: 70px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
div.play-controls {
|
||||
background-color: rgb(23, 24, 26);;
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
width: 100%;
|
||||
height: 40px;
|
||||
box-shadow: 0px 3px 5px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
div.play-buttons {
|
||||
position: relative;
|
||||
width: 100px;
|
||||
margin: 0 auto;
|
||||
padding: 4px;
|
||||
}
|
||||
|
||||
div.play-button img {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
float: left;
|
||||
cursor: pointer;
|
||||
background-color: #37393E;
|
||||
margin: 1px;
|
||||
border-radius: 2px;
|
||||
transition: background-color 100ms;
|
||||
}
|
||||
|
||||
div.player-active img {
|
||||
background-color: #777;
|
||||
}
|
||||
|
||||
div.perf-summary {
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
right: 0px;
|
||||
width: 200px;
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
div.perf-clock {
|
||||
position: relative;
|
||||
float: left;
|
||||
margin: 0 auto;
|
||||
left: 10px;
|
||||
top: -1px;
|
||||
|
||||
padding: 2px;
|
||||
padding-left: 10px;
|
||||
padding-right: 4px;
|
||||
font-size: 18px;
|
||||
|
||||
border-radius: 5px;
|
||||
border-color: #37393E;
|
||||
border-style: solid;
|
||||
border-width: 1px;
|
||||
|
||||
background-color: #000;
|
||||
color: #777;
|
||||
}
|
||||
|
||||
div.perf-clock-active {
|
||||
color: #5BE595;
|
||||
}
|
||||
|
||||
div.perf-clock img {
|
||||
position: relative;
|
||||
top: 2px;
|
||||
/* float: left; */
|
||||
|
||||
height: 17px;
|
||||
}
|
||||
|
||||
div.perf-fps {
|
||||
position: relative;
|
||||
top: 7px;
|
||||
right: 35px;
|
||||
width: 80px;
|
||||
background-color: #000;
|
||||
border-color: #37393E;
|
||||
border-style: solid;
|
||||
border-width: 1px;
|
||||
border-radius: 5px;
|
||||
min-height: 25px;
|
||||
}
|
||||
|
||||
span.perf-fps-value {
|
||||
position: absolute;
|
||||
border-radius: 5px;
|
||||
background-color: #000;
|
||||
font-size: 18px;
|
||||
padding: 2px;
|
||||
padding-left: 8px;
|
||||
}
|
||||
|
||||
span.perf-label {
|
||||
position: absolute;
|
||||
right: 0px;
|
||||
background-color: #37393E;
|
||||
border-top-right-radius: 3px;
|
||||
border-bottom-right-radius: 3px;
|
||||
font-size: 12px;
|
||||
padding: 5px;
|
||||
padding-top: 6px;
|
||||
}
|
||||
|
||||
span.perf-fps-ok {
|
||||
color: #5BE595;
|
||||
}
|
||||
|
||||
div.perf-load {
|
||||
position: absolute;
|
||||
right: 10px;
|
||||
top: 7px;
|
||||
height: 25px;
|
||||
width: 130px;
|
||||
|
||||
border-color: #37393E;
|
||||
border-style: solid;
|
||||
border-width: 1px;
|
||||
border-radius: 5px;
|
||||
background-color: #000;
|
||||
}
|
||||
|
||||
div.perf-load svg {
|
||||
position: absolute;
|
||||
background-color: #000;
|
||||
border-top-left-radius: 4px;
|
||||
border-bottom-left-radius: 4px;
|
||||
}
|
||||
|
||||
div.metric-row:after {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
div.metric-row:after {
|
||||
content: "";
|
||||
display: table;
|
||||
clear: both;
|
||||
margin: 0px;
|
||||
padding: 0px;
|
||||
}
|
||||
|
||||
div.metric-column {
|
||||
float: left;
|
||||
width: 10%;
|
||||
min-width: 150px;
|
||||
margin-right: 0px !important;
|
||||
margin-bottom: 0px !important;
|
||||
}
|
||||
|
||||
div.metric {
|
||||
background-color: #1E2022;
|
||||
color: #eee;
|
||||
font-size: 18px;
|
||||
padding: 10px;
|
||||
margin: 10px;
|
||||
|
||||
border-left-color: #181818;
|
||||
border-left-style: solid;
|
||||
border-left-width: 5px;
|
||||
}
|
||||
|
||||
div.metric-content {
|
||||
font-size: 36px;
|
||||
color: #aaa;
|
||||
}
|
||||
|
||||
div.metric-header {
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
input {
|
||||
-webkit-appearance: none;
|
||||
-moz-appearance: none;
|
||||
border-style: none;
|
||||
outline: none;
|
||||
padding: 5px;
|
||||
}
|
After Width: | Height: | Size: 22 KiB |
After Width: | Height: | Size: 778 B |
After Width: | Height: | Size: 781 B |
After Width: | Height: | Size: 2.6 KiB |
After Width: | Height: | Size: 1.5 KiB |
After Width: | Height: | Size: 1.1 KiB |
After Width: | Height: | Size: 16 KiB |
After Width: | Height: | Size: 769 B |
After Width: | Height: | Size: 937 B |
After Width: | Height: | Size: 897 B |
After Width: | Height: | Size: 1.3 KiB |
After Width: | Height: | Size: 351 B |
After Width: | Height: | Size: 867 B |
After Width: | Height: | Size: 2.0 KiB |
After Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 120 B |
After Width: | Height: | Size: 1.1 KiB |
|
@ -0,0 +1,27 @@
|
|||
<html>
|
||||
<head>
|
||||
<title>Flecs Dash</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700,400italic" rel="stylesheet" type="text/css">
|
||||
<link href="https://fonts.googleapis.com/css?family=Source+Sans+Pro:300,400,600" rel="stylesheet" type="text/css">
|
||||
<link rel="icon" href="favicon.png" sizes="16x16" type="image/png">
|
||||
<link rel="stylesheet" href="css/style.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="app">
|
||||
<sidebar v-on:app-select="app_select" :app="app" :apps="apps"></sidebar>
|
||||
<appcontent :app="app"></appcontent>
|
||||
<play-controls></play-controls>
|
||||
</div>
|
||||
|
||||
<script src="js/vue.js"></script>
|
||||
<script src="js/periodic_request.js"></script>
|
||||
<script src="js/sidebar.js"></script>
|
||||
<script src="js/sidebar-button.js"></script>
|
||||
<script src="js/appcontent.js"></script>
|
||||
<script src="js/perf_summary.js"></script>
|
||||
<script src="js/play_controls.js"></script>
|
||||
<script src="js/app.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/chart.js@2.8.0"></script>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,136 @@
|
|||
|
||||
Vue.config.devtools = true;
|
||||
|
||||
var app = new Vue({
|
||||
el: '#app',
|
||||
methods: {
|
||||
// Request
|
||||
request(method, url, onmsg, onloadend) {
|
||||
const Http = new XMLHttpRequest();
|
||||
|
||||
Http.open(method, "http://" + url);
|
||||
if (onloadend) {
|
||||
Http.onloadend = function() { onloadend(Http); };
|
||||
}
|
||||
Http.send();
|
||||
Http.onreadystatechange = (e)=>{
|
||||
if (Http.readyState == 4) {
|
||||
if (Http.responseText && Http.responseText.length) {
|
||||
if (onmsg) {
|
||||
onmsg(JSON.parse(Http.responseText));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// Request to own server
|
||||
request_self(method, url, onmsg, onloadend) {
|
||||
this.request(method, this.host + "/" + url, onmsg, onloadend);
|
||||
},
|
||||
|
||||
set_host(host) {
|
||||
this.host = host;
|
||||
this.init();
|
||||
},
|
||||
|
||||
get(url, onmsg, onloadend) {
|
||||
this.request_self("GET", url, onmsg, onloadend);
|
||||
},
|
||||
|
||||
put(url, onmsg, onloadend) {
|
||||
this.request_self("PUT", url, onmsg, onloadend);
|
||||
},
|
||||
|
||||
post(url, onmsg, onloadend) {
|
||||
this.request_self("POST", url, onmsg, onloadend);
|
||||
},
|
||||
|
||||
delete(url, onmsg, onloadend) {
|
||||
this.request_self("DELETE", url, onmsg, onloadend);
|
||||
},
|
||||
|
||||
path_to_url(path) {
|
||||
return path.replace(/\./g, "/");
|
||||
},
|
||||
|
||||
// Initialize application, send initial request for id of self
|
||||
init() {
|
||||
this.get("this", (msg) => {
|
||||
this.server_id = msg.server_id;
|
||||
this.get("scope/" + this.path_to_url(this.server_id) + "?include=flecs.dash.App", (msg) => {
|
||||
this.apps = msg;
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
// Anything that needs to appen periodically
|
||||
refresh() { },
|
||||
|
||||
// App select event selects a different application
|
||||
app_select(event) {
|
||||
this.app_load(event.name);
|
||||
},
|
||||
|
||||
import(url) {
|
||||
var app = document.getElementById(url);
|
||||
if (!app) {
|
||||
app = document.createElement("script");
|
||||
app.setAttribute("id", url);
|
||||
app.setAttribute("src", url);
|
||||
document.body.appendChild(app);
|
||||
|
||||
return false
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
},
|
||||
|
||||
component_loaded(id) {
|
||||
return Vue.options.components[id] != undefined;
|
||||
},
|
||||
|
||||
// Load app resources
|
||||
app_load(app_id) {
|
||||
if (this.import("apps/" + app_id + "/" + app_id + ".js")) {
|
||||
this.app_loaded(app_id);
|
||||
}
|
||||
},
|
||||
|
||||
// App is loaded
|
||||
app_loaded(app_id, dependencies) {
|
||||
var dependencies_resolved = true;
|
||||
|
||||
// Load dependent components if any
|
||||
if (dependencies) {
|
||||
dependencies.forEach(d => {
|
||||
if (!this.component_loaded(d.name)) {
|
||||
app.import(d.url);
|
||||
dependencies_resolved = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// If all dependencies are resolved, just load the app
|
||||
if (dependencies_resolved) {
|
||||
this.app = app_id;
|
||||
} else {
|
||||
// If not all dependencies are loaded, try again in a little bit
|
||||
window.setTimeout(function() {
|
||||
this.app_loaded(app_id, dependencies);
|
||||
}.bind(this), 32);
|
||||
}
|
||||
}
|
||||
},
|
||||
data: {
|
||||
host: window.location.host,
|
||||
app: "",
|
||||
apps: [],
|
||||
server_id: ""
|
||||
}
|
||||
});
|
||||
|
||||
window.onload = function() {
|
||||
app.init();
|
||||
window.setInterval(app.refresh, 1000);
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
Vue.component('appcontent', {
|
||||
props: ['world', 'app'],
|
||||
render: function(h) {
|
||||
if (!this.app || this.app === "") {
|
||||
return h('div');
|
||||
} else {
|
||||
return h('div', {
|
||||
attrs: {class: "content"}
|
||||
}, [
|
||||
h('app-' + this.app, {
|
||||
props: {
|
||||
world: this.world
|
||||
}
|
||||
})
|
||||
]);
|
||||
}
|
||||
}
|
||||
});
|
|
@ -0,0 +1,182 @@
|
|||
|
||||
Vue.component('perf-fps', {
|
||||
props: ['data'],
|
||||
data: function() {
|
||||
return {
|
||||
last_tick: 0,
|
||||
last_fps: 0,
|
||||
last_update: 0,
|
||||
last_result: 0
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
fps() {
|
||||
if (!this.data) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
var d = new Date();
|
||||
var t = d.getTime();
|
||||
|
||||
if (t - this.last_update < 1000) {
|
||||
return this.last_result;
|
||||
}
|
||||
|
||||
this.last_update = t;
|
||||
|
||||
const fps = this.data.data["flecs.dash.monitor.Fps"];
|
||||
const tick = this.data.data["flecs.dash.monitor.Tick"];
|
||||
|
||||
let result = (fps.count - this.last_fps) / (tick.count - this.last_tick);
|
||||
|
||||
this.last_fps = fps.count;
|
||||
this.last_tick = tick.count;
|
||||
|
||||
if (result < 0) {
|
||||
result = 0;
|
||||
}
|
||||
|
||||
if (result > 1000000) {
|
||||
this.last_result = (result / 1000000).toFixed(2) + "M";
|
||||
} else
|
||||
if (result > 1000) {
|
||||
this.last_result = (result / 1000).toFixed(2) + "K";
|
||||
} else if (result > 100) {
|
||||
this.last_result = result.toFixed(0);
|
||||
} else {
|
||||
this.last_result = result.toFixed(1);
|
||||
}
|
||||
|
||||
return this.last_result;
|
||||
},
|
||||
css() {
|
||||
if (!this.data) {
|
||||
return "";
|
||||
}
|
||||
|
||||
const fps = this.data.data["flecs.dash.monitor.Fps"];
|
||||
if (this.fps >= fps.target * 0.9) {
|
||||
return "perf-fps-ok";
|
||||
}
|
||||
}
|
||||
},
|
||||
template: `
|
||||
<div>
|
||||
<div class="perf-fps">
|
||||
<span :class="'perf-fps-value ' + css">{{ fps }}</span>
|
||||
<span class="perf-label">FPS</span>
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
});
|
||||
|
||||
Vue.component('perf-load', {
|
||||
props: ['data'],
|
||||
data: function() {
|
||||
return {
|
||||
last_total: 0,
|
||||
last_frame: 0
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
load() {
|
||||
if (!this.data) {
|
||||
return 100;
|
||||
}
|
||||
|
||||
const load = this.data.data["flecs.dash.monitor.Load"];
|
||||
|
||||
let result = (load.frame_time_count - this.last_frame) / (load.total_time_count - this.last_total);
|
||||
this.last_frame = load.frame_time_count;
|
||||
this.last_total = load.total_time_count;
|
||||
|
||||
if (result < 0) {
|
||||
result = 0;
|
||||
}
|
||||
|
||||
if (!result) {
|
||||
result = 1;
|
||||
}
|
||||
|
||||
return (result * 100);
|
||||
},
|
||||
css() {
|
||||
return "";
|
||||
}
|
||||
},
|
||||
template: `
|
||||
<div :class="'perf-load'">
|
||||
<svg width="100px" height="25px">
|
||||
<defs>
|
||||
<linearGradient id="grad1" x1="0%" y1="0%" :x2="1.0 / (load / 100)" y2="0%">
|
||||
<stop offset="0%" style="stop-color:#5BE595;stop-opacity:1" />
|
||||
<stop offset="100%" style="stop-color:#4981B5;stop-opacity:1" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<rect x="0" y="0" :width="5 + (load * 0.95)" height="25" fill="url(#grad1)" />
|
||||
</svg>
|
||||
<span class="perf-label">Load</span>
|
||||
</div>
|
||||
`
|
||||
});
|
||||
|
||||
Vue.component('perf-clock', {
|
||||
props: ['data'],
|
||||
methods: {
|
||||
digit(num) {
|
||||
if (num < 10) {
|
||||
return "0" + num;
|
||||
} else {
|
||||
return num;
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
clock() {
|
||||
if (!this.data) {
|
||||
return "00:00:00.00";
|
||||
}
|
||||
|
||||
const clock = this.data.data["flecs.dash.monitor.WorldClock"];
|
||||
|
||||
let hours = Math.floor(clock.world_time / 3600);
|
||||
let minutes = Math.floor((clock.world_time - hours * 3600) / 60);
|
||||
let seconds = Math.floor(clock.world_time - hours * 3600 - minutes * 60);
|
||||
let remainder = Math.floor((clock.world_time - Math.floor(clock.world_time)) * 10);
|
||||
|
||||
return this.digit(hours) + ":" + this.digit(minutes) + ":" +
|
||||
this.digit(seconds) + "." + remainder;
|
||||
},
|
||||
css() {
|
||||
if (!this.data) {
|
||||
return "";
|
||||
}
|
||||
|
||||
const clock = this.data.data["flecs.dash.monitor.WorldClock"];
|
||||
if (clock.world_time != 0) {
|
||||
return "perf-clock-active";
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
},
|
||||
template: `
|
||||
<div :class="'perf-clock ' + css">
|
||||
{{ clock }}
|
||||
<img src="images/clock.png">
|
||||
</div>
|
||||
`
|
||||
});
|
||||
|
||||
Vue.component('perf-summary', {
|
||||
props: ['data'],
|
||||
template: `
|
||||
<div>
|
||||
<perf-clock :data="data"></perf-clock>
|
||||
<div class="perf-summary">
|
||||
<perf-fps :data="data"></perf-fps>
|
||||
<perf-load :data="data"></perf-load>
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
});
|
|
@ -0,0 +1,36 @@
|
|||
|
||||
class PeriodicRequest {
|
||||
constructor(interval, callback, err_callback) {
|
||||
this.error = false;
|
||||
this.timer = undefined;
|
||||
this.result = undefined;
|
||||
this.interval = interval;
|
||||
this.callback = callback;
|
||||
this.err_callback = err_callback;
|
||||
}
|
||||
|
||||
request(url) {
|
||||
app.get(url, (msg) => {
|
||||
this.result = msg;
|
||||
this.error = false;
|
||||
this.callback(msg);
|
||||
}, (Http) => {
|
||||
if (!Http.status || Http.status >= 400) {
|
||||
this.err_callback(Http.status);
|
||||
this.error = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
stopRequesting() {
|
||||
this.result = undefined;
|
||||
clearInterval(this.timer);
|
||||
}
|
||||
|
||||
startRequesting(url) {
|
||||
this.stopRequesting();
|
||||
this.timer = window.setInterval(function() {
|
||||
this.request(url);
|
||||
}.bind(this), this.interval);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,69 @@
|
|||
|
||||
Vue.component('play-button', {
|
||||
props: ["state", "icon"],
|
||||
methods: {
|
||||
clicked: function() {
|
||||
this.$emit('click', {event: this.icon});
|
||||
},
|
||||
css() {
|
||||
let result = "play-button";
|
||||
if (this.state == this.icon) {
|
||||
result += " player-active";
|
||||
}
|
||||
return result;
|
||||
}
|
||||
},
|
||||
template: `
|
||||
<div :class="css()">
|
||||
<img :src="'images/' + icon + '.png'" v-on:click="clicked">
|
||||
</div>
|
||||
`
|
||||
});
|
||||
|
||||
Vue.component('play-controls', {
|
||||
mounted: function() {
|
||||
this.request.startRequesting("entity/flecs/core/World");
|
||||
},
|
||||
beforeDestroy: function() {
|
||||
this.stopRequesting();
|
||||
},
|
||||
data: function() {
|
||||
return {
|
||||
state: undefined,
|
||||
request: new PeriodicRequest(100, this.on_recv_world, this.on_recv_err),
|
||||
data: undefined
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
play: function(e) {
|
||||
this.state = e.event;
|
||||
app.put("player/" + e.event, (msg) => { });
|
||||
},
|
||||
on_recv_world(data) {
|
||||
this.data = data;
|
||||
if (!this.state) {
|
||||
const state = this.data.data["flecs.player.Player"];
|
||||
if (state.state == "EcsPlayerPlay") {
|
||||
this.state = "play";
|
||||
} else if (state.state == "EcsPlayerStop") {
|
||||
this.state = "stop";
|
||||
} else if (state.state == "EcsPlayerPause") {
|
||||
this.state = "pause";
|
||||
}
|
||||
}
|
||||
},
|
||||
on_recv_err() {
|
||||
this.state = undefined;
|
||||
}
|
||||
},
|
||||
template: `
|
||||
<div class="play-controls">
|
||||
<div class="play-buttons">
|
||||
<play-button icon="stop" :state="state" v-on:click="play"></play-button>
|
||||
<play-button icon="play" :state="state" v-on:click="play"></play-button>
|
||||
<play-button icon="pause" :state="state" v-on:click="play"></play-button>
|
||||
</div>
|
||||
<perf-summary :data="data"></perf-summary>
|
||||
</div>
|
||||
`
|
||||
});
|
|
@ -0,0 +1,46 @@
|
|||
Vue.component('sidebar-button', {
|
||||
props: {
|
||||
app: String,
|
||||
icon: String,
|
||||
active_app: String
|
||||
},
|
||||
data: function() {
|
||||
return {
|
||||
hover: false
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
selector_css() {
|
||||
let result = "sidebar-selector";
|
||||
if (this.app == this.active_app) {
|
||||
result += " sidebar-selector-active";
|
||||
} else if (this.hover) {
|
||||
result += " sidebar-selector-hover";
|
||||
}
|
||||
return result;
|
||||
},
|
||||
button_css() {
|
||||
return "sidebar-button";
|
||||
},
|
||||
select_app: function() {
|
||||
this.$emit('select-app', {name: this.app});
|
||||
},
|
||||
set_hover(hover) {
|
||||
this.hover = hover;
|
||||
}
|
||||
},
|
||||
template: `
|
||||
<div class="sidebar-button-container">
|
||||
<div :class="selector_css()">
|
||||
</div>
|
||||
|
||||
<div :class="button_css()"
|
||||
v-on:click="select_app"
|
||||
v-on:mouseover="set_hover(true)"
|
||||
v-on:mouseleave="set_hover(false)">
|
||||
|
||||
<img :src="icon" class="sidebar-icon">
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
});
|
|
@ -0,0 +1,59 @@
|
|||
Vue.component('sidebar', {
|
||||
props: {
|
||||
app: String,
|
||||
apps: Array
|
||||
},
|
||||
methods: {
|
||||
select_app(event) {
|
||||
this.$emit("app-select", event);
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
app_names: function() {
|
||||
let ids = [];
|
||||
|
||||
if (!this.apps) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.apps.forEach((table) => {
|
||||
if (table.data && table.data.Name) {
|
||||
table.data.Name.forEach((app) => {
|
||||
ids.push(app.value);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return ids;
|
||||
},
|
||||
app_icons: function() {
|
||||
let ids = [];
|
||||
|
||||
if (!this.apps) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.apps.forEach((table) => {
|
||||
if (table.data && table.data["flecs.dash.App"]) {
|
||||
table.data["flecs.dash.App"].forEach((app) => {
|
||||
ids.push(app.icon);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return ids;
|
||||
}
|
||||
},
|
||||
template: `
|
||||
<div class="sidebar">
|
||||
<sidebar-button v-for="(el, i) in app_names"
|
||||
:app="el"
|
||||
:icon="app_icons[i]"
|
||||
:active_app="app"
|
||||
:key="el"
|
||||
v-on:select-app="select_app">
|
||||
</sidebar-button>
|
||||
</div>
|
||||
`
|
||||
});
|
||||
|