Compare commits

...

20 Commits

Author SHA1 Message Date
5548e0f4a8 unicode fonts support 2024-07-30 21:21:30 +03:00
056904fab3 SDL_TRY 2024-07-30 20:18:54 +03:00
9fc98d2caa precompiled.config 2024-07-30 00:56:13 +03:00
4b78ecf617 README 2024-07-30 00:54:03 +03:00
74301a7554 compile_resources 2024-07-30 00:28:34 +03:00
19a953047e clean_additions.sh 2024-07-30 00:10:48 +03:00
1f59909054 deleted embed_fonts.sh 2024-07-23 02:57:26 +05:00
18cba33cff small fix 2024-07-23 02:51:43 +05:00
5ca2407325 resource_embedder 2024-07-22 21:58:29 +03:00
ee2846c737 vscode c_cpp_properties 2024-07-21 06:43:08 +03:00
1644aa4e9b migration to gitea and cbuild 2.1 2024-07-21 06:35:17 +03:00
461b92ccc3 gui error handler 2024-05-06 17:02:50 +05:00
da1dfd4c13 RBTree implementation with const null_node 2024-05-06 16:18:11 +05:00
2050eb4d46 c++17 2024-05-06 16:01:48 +05:00
d4d380cc9f fixed pvs-studio warnings 2024-05-05 15:06:27 +05:00
f9bd8c1184 removed -static arg (can't compile on linux) 2024-05-02 08:45:27 +05:00
306419cd65 NodeEditor -> GraphEditor 2024-05-02 07:23:13 +05:00
fa004284e6 node editor with RBTree 2024-05-02 07:20:58 +05:00
814d88737e RBTree iterator 2024-05-02 02:40:33 +05:00
732633451e assert 2024-05-02 02:37:28 +05:00
54 changed files with 1403 additions and 1113 deletions

18
.gitignore vendored
View File

@@ -1,9 +1,8 @@
# build results # build results
bin/ bin/
obj/ obj/
libs/ src/generated/
*.log imgui.ini
*.tmp
# IDE files # IDE files
.vs/ .vs/
@@ -12,8 +11,13 @@ libs/
*.user *.user
*.vcxproj.filters *.vcxproj.filters
# user files # other files
.old*/ .old*/
current.config old/
src/generated/ tmp/
imgui.ini temp/
*.tmp
*.temp
logs/
log/
*.log

25
.gitmodules vendored
View File

@@ -1,18 +1,13 @@
[submodule "cbuild"] [submodule "dependencies/kerep"]
path = cbuild
url = https://github.com/Timerix22/cbuild.git
[submodule "kerep"]
path = dependencies/kerep path = dependencies/kerep
url = https://github.com/Timerix22/kerep.git url = https://timerix.ddns.net:3322/Timerix/kerep.git
[submodule "SDL2"] [submodule "dependencies/imgui"]
path = dependencies/SDL2
url = https://github.com/Timerix22/SDL.git
branch = SDL2
[submodule "imgui"]
path = dependencies/imgui path = dependencies/imgui
url = https://github.com/Timerix22/imgui.git url = https://github.com/ocornut/imgui.git
branch = docking_cbuild branch = docking
[submodule "imnodes"] [submodule "dependencies/imnodes"]
path = dependencies/imnodes path = dependencies/imnodes
url = https://github.com/Timerix22/imnodes url = https://github.com/Nelarius/imnodes.git
branch = master_cbuild [submodule "dependencies/resource_embedder"]
path = dependencies/resource_embedder
url = https://timerix.ddns.net:3322/Timerix/resource_embedder.git

1
.vscode/.gitignore vendored
View File

@@ -1,2 +1 @@
settings.json settings.json
c_cpp_properties.json

17
.vscode/c_cpp_properties.json vendored Normal file
View File

@@ -0,0 +1,17 @@
{
"configurations": [
{
"name": "all",
"defines": [
"SDL_DISABLE_ANALYZE_MACROS",
"DEBUG=1"
],
"includePath": [
"dependencies/include/SDL2",
"dependencies/imgui",
"${default}"
]
}
],
"version": 4
}

26
.vscode/launch.json vendored
View File

@@ -2,33 +2,11 @@
"version": "0.2.0", "version": "0.2.0",
"configurations": [ "configurations": [
{ {
"name": "gdb_debug_unix", "name": "gdb_debug",
"type": "cppdbg", "type": "cppdbg",
"request": "launch", "request": "launch",
"program": "${workspaceFolder}/bin/GraphC", "program": "${workspaceFolder}/bin/GraphC",
"preLaunchTask": "build_exec_dbg", "windows": { "program": "${workspaceFolder}/bin/GraphC.exe" },
"stopAtEntry": false,
"cwd": "${workspaceFolder}/bin",
"externalConsole": false,
"MIMode": "gdb",
"miDebuggerPath": "gdb",
"setupCommands": [
{
"text": "-enable-pretty-printing",
"ignoreFailures": true
},
{
"text": "-gdb-set disassembly-flavor intel",
"ignoreFailures": true
}
]
},
// the only difference is .exe
{
"name": "gdb_debug_win",
"type": "cppdbg",
"request": "launch",
"program": "${workspaceFolder}/bin/GraphC.exe",
"preLaunchTask": "build_exec_dbg", "preLaunchTask": "build_exec_dbg",
"stopAtEntry": false, "stopAtEntry": false,
"cwd": "${workspaceFolder}/bin", "cwd": "${workspaceFolder}/bin",

5
.vscode/tasks.json vendored
View File

@@ -7,9 +7,10 @@
"label": "build_exec_dbg", "label": "build_exec_dbg",
"detail": "build project with debug symbols", "detail": "build project with debug symbols",
"type": "cppbuild", "type": "cppbuild",
"command": "make", "command": "bash",
"args": [ "args": [
"build_exec_dbg" "-c",
"cbuild build_exec_dbg"
], ],
"options": { "options": {
"cwd": "${workspaceFolder}" "cwd": "${workspaceFolder}"

View File

@@ -1,91 +0,0 @@
######################################
###### Build tasks #######
######################################
default: build_exec_dbg
# creates executable using profile info generated by profile
build_exec: # profile
@cbuild/call_task.sh build_exec 2>&1 | tee -a make_raw.log
# creates executable with debug info and no optimizations
build_exec_dbg:
@cbuild/call_task.sh build_exec_dbg 2>&1 | tee make_raw.log
######################################
###### Rebuild dependencies #######
######################################
# recompile kerep.a in the next build task
rebuild_kerep:
@cbuild/rebuild_dep.sh libkerep.a 2>&1 | tee make_raw.log
# recompile imgui.a in the next build task
rebuild_imgui:
@cbuild/rebuild_dep.sh libimgui.a 2>&1 | tee make_raw.log
rebuild_imnodes:
@cbuild/rebuild_dep.sh libimnodes.a 2>&1 | tee make_raw.log
# writes ttf fonts fron ./fonts/ to C compressed arrays in C source files
# builds static library from font arrays definitions
embed_fonts:
@cbuild/call_task.sh embed_fonts
rebuild_all: rebuild_kerep rebuild_imgui rebuild_imnodes embed_fonts
######################################
###### Launch tasks #######
######################################
# executes $EXEC_FILE
exec: build_exec
@cbuild/call_task.sh exec 2>&1 | tee -a make_raw.log
# executes $EXEC_FILE
exec_dbg: build_exec_dbg
@cbuild/call_task.sh exec 2>&1 | tee -a make_raw.log
# executes $EXEC_FILE with valgrind memory checker
valgrind: build_exec_dbg
@cbuild/call_task.sh valgrind 2>&1 | tee -a make_raw.log
# generates profiling info
profile:
@cbuild/call_task.sh profile 2>&1 | tee make_raw.log
# compiles program with -pg and runs it with gprof
# uses gprof2dot python script to generate function call tree (pip install gprof2dot)
# requires graphviz (https://www.graphviz.org/download/source/)
gprof:
@cbuild/call_task.sh gprof 2>&1 | tee make_raw.log
# compiles program and runs it with callgrind (part of valgrind)
# uses gprof2dot python script to generate function call tree (pip install gprof2dot)
# requires graphviz (https://www.graphviz.org/download/source/)
# P.S. detailed results can be viewed in KCacheGrind
callgrind:
@cbuild/call_task.sh callgrind 2>&1 | tee make_raw.log
# compiles executable with sanitizers and executes it to find errors and warnings
sanitize:
@cbuild/call_task.sh sanitize 2>&1 | tee make_raw.log
######################################
###### Other tasks #######
######################################
# deletes generated files
clean:
@cbuild/call_task.sh clean 2>&1 | tee make_raw.log
# removes all unreadable characters copied from stdio
fix_log:
sed 's/[^[:blank:][:print:]]//g' make_raw.log \
| sed 's/\[0;[0-9][0-9]m//g' \
| sed 's/\[0;[0-9]m//g' \
| sed 's/\[[0-9][0-9]m//g' \
| sed 's/\[[0-9]m//g' \
| sed 's/ H //g' \
| sed 's/\[3gH //g' \
> make_fixed.log

View File

@@ -5,30 +5,36 @@ GUI is based on [Dear ImGui](https://github.com/ocornut/imgui) and [SDL](https:/
## Installation ## Installation
1. Clone the repository 1. Clone the repository
```shell ```sh
git clone --recurse-submodules https://github.com/Timerix22/GraphC git clone --recurse-submodules https://timerix.ddns.net:3322/Timerix/GraphC.git
``` ```
2. Install **SDL2** from package manager or compile it from source. 2. Install [cbuild](https://timerix.ddns.net:3322/Timerix/cbuild.git)
**If you are using msys, switch to mingw64 shell.** 3. Install **SDL2** from package manager or compile it from source.
```shell **If you are using msys, switch to mingw64 sh.**
cd dependencies/SDL2 ```sh
git clone https://github.com/libsdl-org/SDL.git
cd SDL
./configure ./configure
make -j [number of cpu threads] make -j [number of cpu threads]
``` ```
Then you can install it systemwide: Then you can install it systemwide (on **Linux**):
```shell ```sh
make install sudo make install
``` ```
or copy to ./libs: or copy to ./dependencies/precompiled/ (on **Windows**):
```shell ```sh
mkdir -p ../../libs mkdir -p ../GraphC/dependencies/precompiled/
cp ./build/.libs/SDL2.dll ../../libs/ cp ./build/.libs/SDL2.dll ../GraphC/dependencies/precompiled/
```
If it doesn't work, read [SDL/INSTALL.txt](https://github.com/libsdl-org/SDL/blob/SDL2/INSTALL.txt) and [SDL/docs/README.md](https://github.com/libsdl-org/SDL/blob/SDL2/docs/README.md).
4. Symlink SDL headers directory to `dependencies/include`
```sh
cd ../GraphC
ln -s SDL2_HEADERS_DIRECTORY_ABSOLUTE_PATH -T dependencies/include/SDL2
```
Location of the headers can be found by `pkg-config --cflags --libs sdl2`.
Mingw installs SDL2 headers to `/mingw64/include/SDL2`.
5. Compile the program
```sh
cbuild build_exec_dbg
``` ```
If it didn't work, read [SDL docs](dependencies/SDL2/docs/README.md) and [INSTALL.txt](dependencies/SDL2/INSTALL.txt).
3. Compile the program
```shell
make build_exec
```
4. Copy program files from `bin/*` to any directory

1
cbuild

Submodule cbuild deleted from 574ce6eab3

2
dependencies/.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
/include/
/precompiled/

1
dependencies/SDL2 vendored

Submodule dependencies/SDL2 deleted from 903d888cc3

16
dependencies/compile_resources.sh vendored Executable file
View File

@@ -0,0 +1,16 @@
#!/usr/bin/env bash
set -e
echo 'compiling resources...'
inputs=""
for f in $(find ../../resources -type f); do
inputs+="-i $f "
done
mkdir -p ../obj
mkdir -p ../bin
set -x
./resource_embedder -o ../obj/resources.c -d ../../resources $inputs
gcc -Wall -Wextra -O2 -DEMBEDDED_RESOURCE_DEFINITION -c ../obj/resources.c -o ../bin/resources.o
set +x
mkdir -p ../../src/generated
cat ../obj/resources.c | head -n42 > ../../src/generated/resources.h
echo "embedded resources header was created at src/generated/resources.h"

12
dependencies/imgui.config vendored Executable file
View File

@@ -0,0 +1,12 @@
#!/usr/bin/env bash
DEP_WORKING_DIR='dependencies/imgui'
DEP_PRE_BUILD_COMMAND=''
if [[ "$TASK" = *_dbg ]]; then
DEP_BUILD_COMMAND='cbuild -c ../imgui.project.config build_static_lib_dbg'
else
DEP_BUILD_COMMAND='cbuild -c ../imgui.project.config build_static_lib'
fi
DEP_POST_BUILD_COMMAND=''
DEP_CLEAN_COMMAND='cbuild clean -c ../imgui.project.config'
DEP_STATIC_OUT_FILES='../bin/libimgui.a'
DEP_DYNAMIC_OUT_FILES=''

101
dependencies/imgui.project.config vendored Executable file
View File

@@ -0,0 +1,101 @@
#!/usr/bin/env bash
CBUILD_VERSION=2.1.2
CONFIG_VERSION=1
PROJECT="imgui"
CMP_C="gcc"
CMP_CPP="g++"
STD_C="c11"
STD_CPP="c++11"
WARN_C="-Wall -Wno-discarded-qualifiers -Wno-unused-parameter"
WARN_CPP="-Wall -Wno-unused-parameter"
SRC_C=""
SRC_CPP="imgui.cpp
imgui_demo.cpp
imgui_draw.cpp
imgui_tables.cpp
imgui_widgets.cpp
backends/imgui_impl_sdl2.cpp
backends/imgui_impl_opengl3.cpp"
# Directory with dependency configs.
# See cbuild/example_dependency_configs
DEPENDENCY_CONFIGS_DIR='dependencies'
# List of dependency config files in DEPENDENCY_CONFIGS_DIR separated by space.
ENABLED_DEPENDENCIES=''
# OBJDIR structure:
# ├── objects/ - Compiled object files. Cleans on each call of build task
# ├── static_libs/ - Symbolic links to static libraries used by linker. Cleans on each call of build task.
# ├── static_libs/ - Symbolic links to dynamic libraries used by linker. Cleans on each call of build task.
# └── profile/ - gcc *.gcda profiling info files
OBJDIR="../obj"
OUTDIR="../bin"
STATIC_LIB_FILE="lib$PROJECT.a"
# header include directories
INCLUDE="-I. -I../include/SDL2"
# OS-specific options
case "$OS" in
WINDOWS)
EXEC_FILE="$PROJECT.exe"
SHARED_LIB_FILE="lib$PROJECT.dll"
;;
LINUX)
EXEC_FILE="$PROJECT"
SHARED_LIB_FILE="lib$PROJECT.so"
;;
*)
error "operating system $OS has no configuration variants"
;;
esac
# TASKS
case "$TASK" in
# creates shared library
build_shared_lib)
C_ARGS="-O2 -fpic -flto -shared"
CPP_ARGS="$C_ARGS"
LINKER_ARGS="$CPP_ARGS -Wl,-soname,$SHARED_LIB_FILE"
PRE_TASK_SCRIPT=
TASK_SCRIPT=cbuild/default_tasks/build_shared_lib.sh
POST_TASK_SCRIPT=
;;
# creates shared library with debug symbols and no optimizations
build_shared_lib_dbg)
C_ARGS="-O0 -g3 -fpic -shared"
CPP_ARGS="$C_ARGS"
LINKER_ARGS="$CPP_ARGS -Wl,-soname,$SHARED_LIB_FILE"
PRE_TASK_SCRIPT=
TASK_SCRIPT=cbuild/default_tasks/build_shared_lib.sh
POST_TASK_SCRIPT=
;;
# creates static library
build_static_lib)
C_ARGS="-O2"
CPP_ARGS="$C_ARGS"
PRE_TASK_SCRIPT=
TASK_SCRIPT=cbuild/default_tasks/build_static_lib.sh
POST_TASK_SCRIPT=
;;
# creates static library with debug symbols and no optimizations
build_static_lib_dbg)
C_ARGS="-O0 -g3"
CPP_ARGS="$C_ARGS"
PRE_TASK_SCRIPT=
TASK_SCRIPT=cbuild/default_tasks/build_static_lib.sh
POST_TASK_SCRIPT=
;;
# deletes generated files
clean)
TASK_SCRIPT=cbuild/default_tasks/clean.sh
;;
# nothing to do
"" | no_task)
;;
# unknown task
*)
error "task <$TASK> not found"
;;
esac

12
dependencies/imnodes.config vendored Executable file
View File

@@ -0,0 +1,12 @@
#!/usr/bin/env bash
DEP_WORKING_DIR='dependencies/imnodes'
DEP_PRE_BUILD_COMMAND=''
if [[ "$TASK" = *_dbg ]]; then
DEP_BUILD_COMMAND='cbuild -c ../imnodes.project.config build_static_lib_dbg'
else
DEP_BUILD_COMMAND='cbuild -c ../imnodes.project.config build_static_lib'
fi
DEP_POST_BUILD_COMMAND=''
DEP_CLEAN_COMMAND='cbuild clean -c ../imnodes.project.config'
DEP_STATIC_OUT_FILES='../bin/libimnodes.a'
DEP_DYNAMIC_OUT_FILES=''

95
dependencies/imnodes.project.config vendored Executable file
View File

@@ -0,0 +1,95 @@
#!/usr/bin/env bash
CBUILD_VERSION=2.1.2
CONFIG_VERSION=1
PROJECT="imnodes"
CMP_C="gcc"
CMP_CPP="g++"
STD_C="c11"
STD_CPP="c++11"
WARN_C="-Wall -Wno-discarded-qualifiers -Wno-unused-parameter"
WARN_CPP="-Wall -Wno-unused-parameter"
SRC_C=""
SRC_CPP="imnodes.cpp"
# Directory with dependency configs.
# See cbuild/example_dependency_configs
DEPENDENCY_CONFIGS_DIR='dependencies'
# List of dependency config files in DEPENDENCY_CONFIGS_DIR separated by space.
ENABLED_DEPENDENCIES=''
# OBJDIR structure:
# ├── objects/ - Compiled object files. Cleans on each call of build task
# ├── static_libs/ - Symbolic links to static libraries used by linker. Cleans on each call of build task.
# ├── static_libs/ - Symbolic links to dynamic libraries used by linker. Cleans on each call of build task.
# └── profile/ - gcc *.gcda profiling info files
OBJDIR="../obj"
OUTDIR="../bin"
STATIC_LIB_FILE="lib$PROJECT.a"
# header include directories
INCLUDE="-I. -I../imgui -I../imgui/backends"
# OS-specific options
case "$OS" in
WINDOWS)
EXEC_FILE="$PROJECT.exe"
SHARED_LIB_FILE="lib$PROJECT.dll"
;;
LINUX)
EXEC_FILE="$PROJECT"
SHARED_LIB_FILE="lib$PROJECT.so"
;;
*)
error "operating system $OS has no configuration variants"
;;
esac
# TASKS
case "$TASK" in
# creates shared library
build_shared_lib)
C_ARGS="-O2 -fpic -flto -shared"
CPP_ARGS="$C_ARGS"
LINKER_ARGS="$CPP_ARGS -Wl,-soname,$SHARED_LIB_FILE"
PRE_TASK_SCRIPT=
TASK_SCRIPT=cbuild/default_tasks/build_shared_lib.sh
POST_TASK_SCRIPT=
;;
# creates shared library with debug symbols and no optimizations
build_shared_lib_dbg)
C_ARGS="-O0 -g3 -fpic -shared"
CPP_ARGS="$C_ARGS"
LINKER_ARGS="$CPP_ARGS -Wl,-soname,$SHARED_LIB_FILE"
PRE_TASK_SCRIPT=
TASK_SCRIPT=cbuild/default_tasks/build_shared_lib.sh
POST_TASK_SCRIPT=
;;
# creates static library
build_static_lib)
C_ARGS="-O2"
CPP_ARGS="$C_ARGS"
PRE_TASK_SCRIPT=
TASK_SCRIPT=cbuild/default_tasks/build_static_lib.sh
POST_TASK_SCRIPT=
;;
# creates static library with debug symbols and no optimizations
build_static_lib_dbg)
C_ARGS="-O0 -g3"
CPP_ARGS="$C_ARGS"
PRE_TASK_SCRIPT=
TASK_SCRIPT=cbuild/default_tasks/build_static_lib.sh
POST_TASK_SCRIPT=
;;
# deletes generated files
clean)
TASK_SCRIPT=cbuild/default_tasks/clean.sh
;;
# nothing to do
"" | no_task)
;;
# unknown task
*)
error "task <$TASK> not found"
;;
esac

14
dependencies/kerep.config vendored Executable file
View File

@@ -0,0 +1,14 @@
#!/usr/bin/env bash
DEP_WORKING_DIR='dependencies/kerep'
DEP_PRE_BUILD_COMMAND=''
if [[ "$TASK" = *_dbg ]]; then
DEP_BUILD_COMMAND='cbuild build_static_lib_dbg'
else
DEP_BUILD_COMMAND='cbuild build_static_lib'
fi
DEP_POST_BUILD_COMMAND=''
DEP_CLEAN_COMMAND='cbuild clean'
# won't be copied to project $OUTDIR
DEP_STATIC_OUT_FILES='bin/libkerep.a'
# will be copied tp project $OUTDIR
DEP_DYNAMIC_OUT_FILES=''

21
dependencies/precompiled.config vendored Executable file
View File

@@ -0,0 +1,21 @@
#!/usr/bin/env bash
mkdir -p 'dependencies/precompiled'
DEP_WORKING_DIR='dependencies/precompiled'
DEP_PRE_BUILD_COMMAND=''
DEP_BUILD_COMMAND=''
DEP_POST_BUILD_COMMAND=''
DEP_CLEAN_COMMAND=''
# won't be copied to project $OUTDIR
DEP_STATIC_OUT_FILES=$(find dependencies/precompiled -name '*.a' | sed 's,dependencies/precompiled/,,')
# will be copied tp project $OUTDIR
case $OS in
WINDOWS)
DEP_DYNAMIC_OUT_FILES=$(find dependencies/precompiled -name '*.dll' | sed 's,dependencies/precompiled/,,')
;;
LINUX)
DEP_DYNAMIC_OUT_FILES=$(find dependencies/precompiled -name '*.so' | sed 's,dependencies/precompiled/,,')
;;
*)
error "operating system $OS has no configuration variants"
;;
esac

8
dependencies/resources.config vendored Executable file
View File

@@ -0,0 +1,8 @@
#!/usr/bin/env bash
DEP_WORKING_DIR='dependencies/resource_embedder'
DEP_PRE_BUILD_COMMAND='./build.sh'
DEP_BUILD_COMMAND='../compile_resources.sh'
DEP_POST_BUILD_COMMAND=''
DEP_CLEAN_COMMAND='rm -f ./resource_embedder ./resource_embedder.exe ../obj/resources.c ../bin/resources.o'
DEP_STATIC_OUT_FILES='../bin/resources.o'
DEP_DYNAMIC_OUT_FILES=

1
fonts/.gitignore vendored
View File

@@ -1 +0,0 @@
generated/

Binary file not shown.

View File

@@ -1,388 +0,0 @@
// dear imgui
// (binary_to_compressed_c.cpp)
// Helper tool to turn a file into a C array, if you want to embed font data in your source code.
// The data is first compressed with stb_compress() to reduce source code size,
// then encoded in Base85 to fit in a string so we can fit roughly 4 bytes of compressed data into 5 bytes of source code (suggested by @mmalex)
// (If we used 32-bit constants it would require take 11 bytes of source code to encode 4 bytes, and be endianness dependent)
// Note that even with compression, the output array is likely to be bigger than the binary file..
// Load compressed TTF fonts with ImGui::GetIO().Fonts->AddFontFromMemoryCompressedTTF()
// Build with, e.g:
// # cl.exe binary_to_compressed_c.cpp
// # g++ binary_to_compressed_c.cpp
// # clang++ binary_to_compressed_c.cpp
// You can also find a precompiled Windows binary in the binary/demo package available from https://github.com/ocornut/imgui
// Usage:
// binary_to_compressed_c.exe [-base85] [-nocompress] [-nostatic] <inputfile> <symbolname>
// Usage example:
// # binary_to_compressed_c.exe myfont.ttf MyFont > myfont.cpp
// # binary_to_compressed_c.exe -base85 myfont.ttf MyFont > myfont.cpp
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <assert.h>
// stb_compress* from stb.h - declaration
typedef unsigned int stb_uint;
typedef unsigned char stb_uchar;
stb_uint stb_compress(stb_uchar* out, stb_uchar* in, stb_uint len);
static bool binary_to_compressed_c(const char* filename, const char* symbol, bool use_base85_encoding, bool use_compression, bool use_static);
int main(int argc, char** argv)
{
if (argc < 3)
{
printf("Syntax: %s [-base85] [-nocompress] [-nostatic] <inputfile> <symbolname>\n", argv[0]);
return 0;
}
int argn = 1;
bool use_base85_encoding = false;
bool use_compression = true;
bool use_static = true;
while (argn < (argc - 2) && argv[argn][0] == '-')
{
if (strcmp(argv[argn], "-base85") == 0) { use_base85_encoding = true; argn++; }
else if (strcmp(argv[argn], "-nocompress") == 0) { use_compression = false; argn++; }
else if (strcmp(argv[argn], "-nostatic") == 0) { use_static = false; argn++; }
else
{
fprintf(stderr, "Unknown argument: '%s'\n", argv[argn]);
return 1;
}
}
bool ret = binary_to_compressed_c(argv[argn], argv[argn + 1], use_base85_encoding, use_compression, use_static);
if (!ret)
fprintf(stderr, "Error opening or reading file: '%s'\n", argv[argn]);
return ret ? 0 : 1;
}
char Encode85Byte(unsigned int x)
{
x = (x % 85) + 35;
return (char)((x >= '\\') ? x + 1 : x);
}
bool binary_to_compressed_c(const char* filename, const char* symbol, bool use_base85_encoding, bool use_compression, bool use_static)
{
// Read file
FILE* f = fopen(filename, "rb");
if (!f) return false;
int data_sz;
if (fseek(f, 0, SEEK_END) || (data_sz = (int)ftell(f)) == -1 || fseek(f, 0, SEEK_SET)) { fclose(f); return false; }
char* data = new char[data_sz + 4];
if (fread(data, 1, data_sz, f) != (size_t)data_sz) { fclose(f); delete[] data; return false; }
memset((void*)(((char*)data) + data_sz), 0, 4);
fclose(f);
// Compress
int maxlen = data_sz + 512 + (data_sz >> 2) + sizeof(int); // total guess
char* compressed = use_compression ? new char[maxlen] : data;
int compressed_sz = use_compression ? stb_compress((stb_uchar*)compressed, (stb_uchar*)data, data_sz) : data_sz;
if (use_compression)
memset(compressed + compressed_sz, 0, maxlen - compressed_sz);
// Output as Base85 encoded
FILE* out = stdout;
fprintf(out, "// File: '%s' (%d bytes)\n", filename, (int)data_sz);
fprintf(out, "// Exported using binary_to_compressed_c.cpp\n");
const char* static_str = use_static ? "static " : "";
const char* compressed_str = use_compression ? "compressed_" : "";
if (use_base85_encoding)
{
fprintf(out, "%sconst char %s_%sdata_base85[%d+1] =\n \"", static_str, symbol, compressed_str, (int)((compressed_sz + 3) / 4)*5);
char prev_c = 0;
for (int src_i = 0; src_i < compressed_sz; src_i += 4)
{
// This is made a little more complicated by the fact that ??X sequences are interpreted as trigraphs by old C/C++ compilers. So we need to escape pairs of ??.
unsigned int d = *(unsigned int*)(compressed + src_i);
for (unsigned int n5 = 0; n5 < 5; n5++, d /= 85)
{
char c = Encode85Byte(d);
fprintf(out, (c == '?' && prev_c == '?') ? "\\%c" : "%c", c);
prev_c = c;
}
if ((src_i % 112) == 112 - 4)
fprintf(out, "\"\n \"");
}
fprintf(out, "\";\n\n");
}
else
{
fprintf(out, "%sconst unsigned int %s_%ssize = %d;\n", static_str, symbol, compressed_str, (int)compressed_sz);
fprintf(out, "%sconst unsigned int %s_%sdata[%d/4] =\n{", static_str, symbol, compressed_str, (int)((compressed_sz + 3) / 4)*4);
int column = 0;
for (int i = 0; i < compressed_sz; i += 4)
{
unsigned int d = *(unsigned int*)(compressed + i);
if ((column++ % 12) == 0)
fprintf(out, "\n 0x%08x, ", d);
else
fprintf(out, "0x%08x, ", d);
}
fprintf(out, "\n};\n\n");
}
// Cleanup
delete[] data;
if (use_compression)
delete[] compressed;
return true;
}
// stb_compress* from stb.h - definition
//////////////////// compressor ///////////////////////
static stb_uint stb_adler32(stb_uint adler32, stb_uchar *buffer, stb_uint buflen)
{
const unsigned long ADLER_MOD = 65521;
unsigned long s1 = adler32 & 0xffff, s2 = adler32 >> 16;
unsigned long blocklen, i;
blocklen = buflen % 5552;
while (buflen) {
for (i=0; i + 7 < blocklen; i += 8) {
s1 += buffer[0], s2 += s1;
s1 += buffer[1], s2 += s1;
s1 += buffer[2], s2 += s1;
s1 += buffer[3], s2 += s1;
s1 += buffer[4], s2 += s1;
s1 += buffer[5], s2 += s1;
s1 += buffer[6], s2 += s1;
s1 += buffer[7], s2 += s1;
buffer += 8;
}
for (; i < blocklen; ++i)
s1 += *buffer++, s2 += s1;
s1 %= ADLER_MOD, s2 %= ADLER_MOD;
buflen -= blocklen;
blocklen = 5552;
}
return (s2 << 16) + s1;
}
static unsigned int stb_matchlen(stb_uchar *m1, stb_uchar *m2, stb_uint maxlen)
{
stb_uint i;
for (i=0; i < maxlen; ++i)
if (m1[i] != m2[i]) return i;
return i;
}
// simple implementation that just takes the source data in a big block
static stb_uchar *stb__out;
static FILE *stb__outfile;
static stb_uint stb__outbytes;
static void stb__write(unsigned char v)
{
fputc(v, stb__outfile);
++stb__outbytes;
}
//#define stb_out(v) (stb__out ? *stb__out++ = (stb_uchar) (v) : stb__write((stb_uchar) (v)))
#define stb_out(v) do { if (stb__out) *stb__out++ = (stb_uchar) (v); else stb__write((stb_uchar) (v)); } while (0)
static void stb_out2(stb_uint v) { stb_out(v >> 8); stb_out(v); }
static void stb_out3(stb_uint v) { stb_out(v >> 16); stb_out(v >> 8); stb_out(v); }
static void stb_out4(stb_uint v) { stb_out(v >> 24); stb_out(v >> 16); stb_out(v >> 8 ); stb_out(v); }
static void outliterals(stb_uchar *in, int numlit)
{
while (numlit > 65536) {
outliterals(in,65536);
in += 65536;
numlit -= 65536;
}
if (numlit == 0) ;
else if (numlit <= 32) stb_out (0x000020 + numlit-1);
else if (numlit <= 2048) stb_out2(0x000800 + numlit-1);
else /* numlit <= 65536) */ stb_out3(0x070000 + numlit-1);
if (stb__out) {
memcpy(stb__out,in,numlit);
stb__out += numlit;
} else
fwrite(in, 1, numlit, stb__outfile);
}
static int stb__window = 0x40000; // 256K
static int stb_not_crap(int best, int dist)
{
return ((best > 2 && dist <= 0x00100)
|| (best > 5 && dist <= 0x04000)
|| (best > 7 && dist <= 0x80000));
}
static stb_uint stb__hashsize = 32768;
// note that you can play with the hashing functions all you
// want without needing to change the decompressor
#define stb__hc(q,h,c) (((h) << 7) + ((h) >> 25) + q[c])
#define stb__hc2(q,h,c,d) (((h) << 14) + ((h) >> 18) + (q[c] << 7) + q[d])
#define stb__hc3(q,c,d,e) ((q[c] << 14) + (q[d] << 7) + q[e])
static unsigned int stb__running_adler;
static int stb_compress_chunk(stb_uchar *history,
stb_uchar *start,
stb_uchar *end,
int length,
int *pending_literals,
stb_uchar **chash,
stb_uint mask)
{
(void)history;
int window = stb__window;
stb_uint match_max;
stb_uchar *lit_start = start - *pending_literals;
stb_uchar *q = start;
#define STB__SCRAMBLE(h) (((h) + ((h) >> 16)) & mask)
// stop short of the end so we don't scan off the end doing
// the hashing; this means we won't compress the last few bytes
// unless they were part of something longer
while (q < start+length && q+12 < end) {
int m;
stb_uint h1,h2,h3,h4, h;
stb_uchar *t;
int best = 2, dist=0;
if (q+65536 > end)
match_max = (stb_uint)(end-q);
else
match_max = 65536;
#define stb__nc(b,d) ((d) <= window && ((b) > 9 || stb_not_crap((int)(b),(int)(d))))
#define STB__TRY(t,p) /* avoid retrying a match we already tried */ \
if (p ? dist != (int)(q-t) : 1) \
if ((m = stb_matchlen(t, q, match_max)) > best) \
if (stb__nc(m,q-(t))) \
best = m, dist = (int)(q - (t))
// rather than search for all matches, only try 4 candidate locations,
// chosen based on 4 different hash functions of different lengths.
// this strategy is inspired by LZO; hashing is unrolled here using the
// 'hc' macro
h = stb__hc3(q,0, 1, 2); h1 = STB__SCRAMBLE(h);
t = chash[h1]; if (t) STB__TRY(t,0);
h = stb__hc2(q,h, 3, 4); h2 = STB__SCRAMBLE(h);
h = stb__hc2(q,h, 5, 6); t = chash[h2]; if (t) STB__TRY(t,1);
h = stb__hc2(q,h, 7, 8); h3 = STB__SCRAMBLE(h);
h = stb__hc2(q,h, 9,10); t = chash[h3]; if (t) STB__TRY(t,1);
h = stb__hc2(q,h,11,12); h4 = STB__SCRAMBLE(h);
t = chash[h4]; if (t) STB__TRY(t,1);
// because we use a shared hash table, can only update it
// _after_ we've probed all of them
chash[h1] = chash[h2] = chash[h3] = chash[h4] = q;
if (best > 2)
assert(dist > 0);
// see if our best match qualifies
if (best < 3) { // fast path literals
++q;
} else if (best > 2 && best <= 0x80 && dist <= 0x100) {
outliterals(lit_start, (int)(q-lit_start)); lit_start = (q += best);
stb_out(0x80 + best-1);
stb_out(dist-1);
} else if (best > 5 && best <= 0x100 && dist <= 0x4000) {
outliterals(lit_start, (int)(q-lit_start)); lit_start = (q += best);
stb_out2(0x4000 + dist-1);
stb_out(best-1);
} else if (best > 7 && best <= 0x100 && dist <= 0x80000) {
outliterals(lit_start, (int)(q-lit_start)); lit_start = (q += best);
stb_out3(0x180000 + dist-1);
stb_out(best-1);
} else if (best > 8 && best <= 0x10000 && dist <= 0x80000) {
outliterals(lit_start, (int)(q-lit_start)); lit_start = (q += best);
stb_out3(0x100000 + dist-1);
stb_out2(best-1);
} else if (best > 9 && dist <= 0x1000000) {
if (best > 65536) best = 65536;
outliterals(lit_start, (int)(q-lit_start)); lit_start = (q += best);
if (best <= 0x100) {
stb_out(0x06);
stb_out3(dist-1);
stb_out(best-1);
} else {
stb_out(0x04);
stb_out3(dist-1);
stb_out2(best-1);
}
} else { // fallback literals if no match was a balanced tradeoff
++q;
}
}
// if we didn't get all the way, add the rest to literals
if (q-start < length)
q = start+length;
// the literals are everything from lit_start to q
*pending_literals = (int)(q - lit_start);
stb__running_adler = stb_adler32(stb__running_adler, start, (stb_uint)(q - start));
return (int)(q - start);
}
static int stb_compress_inner(stb_uchar *input, stb_uint length)
{
int literals = 0;
stb_uint len,i;
stb_uchar **chash;
chash = (stb_uchar**) malloc(stb__hashsize * sizeof(stb_uchar*));
if (chash == NULL) return 0; // failure
for (i=0; i < stb__hashsize; ++i)
chash[i] = NULL;
// stream signature
stb_out(0x57); stb_out(0xbc);
stb_out2(0);
stb_out4(0); // 64-bit length requires 32-bit leading 0
stb_out4(length);
stb_out4(stb__window);
stb__running_adler = 1;
len = stb_compress_chunk(input, input, input+length, length, &literals, chash, stb__hashsize-1);
assert(len == length);
outliterals(input+length - literals, literals);
free(chash);
stb_out2(0x05fa); // end opcode
stb_out4(stb__running_adler);
return 1; // success
}
stb_uint stb_compress(stb_uchar *out, stb_uchar *input, stb_uint length)
{
stb__out = out;
stb__outfile = NULL;
stb_compress_inner(input, length);
return (stb_uint)(stb__out - out);
}

89
default.config → project.config Normal file → Executable file
View File

@@ -1,50 +1,46 @@
#!/bin/bash #!/usr/bin/env bash
CBUILD_VERSION=7 CBUILD_VERSION=2.1.2
CONFIG_VERSION=6 CONFIG_VERSION=7
PROJECT="GraphC" PROJECT="GraphC"
CMP_C="gcc" CMP_C="gcc"
CMP_CPP="g++" CMP_CPP="g++"
STD_C="c11" STD_C="c11"
STD_CPP="c++11" STD_CPP="c++17"
WARN_C="-Wall -Wno-discarded-qualifiers -Wextra -Wno-unused-parameter" WARN_C="-Wall -Wno-discarded-qualifiers -Wextra -Wno-unused-parameter"
WARN_CPP="-Wall -Wextra -Wno-unused-parameter" WARN_CPP="-Wall -Wextra -Wno-unused-parameter"
SRC_C="$(find src -name '*.c')" SRC_C="$(find src -name '*.c')"
SRC_CPP="$(find src -name '*.cpp')" SRC_CPP="$(find src -name '*.cpp')"
#TESTS_C="$( find tests -name '*.c')"
#TESTS_CPP="$(find tests -name '*.cpp')"
# dir with dependeicy dirs # Directory with dependency configs.
DEPS_BASEDIR="dependencies" # See cbuild/example_dependency_configs
# EXAMPLE: "dependency_dir='build_task out_dir lib_file' DEPENDENCY_CONFIGS_DIR='dependencies'
# other_depndency_dir=..." # List of dependency config files in DEPENDENCY_CONFIGS_DIR separated by space.
# Dependencies must be declared on separate lines ENABLED_DEPENDENCIES='precompiled resources kerep imgui imnodes'
# Values can be override by resetting one of dependencies:
# DEPS="$DEPS
# dependency_dir='...'"
DEPS="kerep='build_static_lib bin libkerep.a'
imgui='build_static_lib bin libimgui.a'
imnodes='build_static_lib bin libimnodes.a'"
# OBJDIR structure: # OBJDIR structure:
# ├── objects - dir where compiled *.o files are stored. cleans every call of build task # ├── objects/ - Compiled object files. Cleans on each call of build task
# ├── profile - dir where gcc *.gcda profiling info files stored # ├── static_libs/ - Symbolic links to static libraries used by linker. Cleans on each call of build task.
# ├── libs - there you can put static libs and linker will find them # ├── static_libs/ - Symbolic links to dynamic libraries used by linker. Cleans on each call of build task.
# └── out - output files are created here and then copied to OUTDIR # └── profile/ - gcc *.gcda profiling info files
OBJDIR="obj" OBJDIR="obj"
OUTDIR="bin" OUTDIR="bin"
STATIC_LIB_FILE="lib$PROJECT.a"
# header include directories
INCLUDE="-I./dependencies/imgui -I./dependencies/include/SDL2"
# OS-specific options # OS-specific options
case "$OS" in case "$OS" in
WINDOWS) WINDOWS)
EXEC_FILE="$PROJECT.exe" EXEC_FILE="$PROJECT.exe"
INCLUDE="-I./dependencies/imgui -I./dependencies/SDL2/include" SHARED_LIB_FILE="$PROJECT.dll"
LINKER_LIBS="-L./libs/ -l:SDL2.dll -lopengl32 -lpthread -lws2_32" LINKER_LIBS="-lopengl32 -lpthread -lws2_32"
;; ;;
LINUX) LINUX)
EXEC_FILE="$PROJECT" EXEC_FILE="$PROJECT"
INCLUDE="-I./dependencies/imgui -I./dependencies/SDL2/include" SHARED_LIB_FILE="$PROJECT.so"
LINKER_LIBS="-L./libs/ -lSDL2 -lGL" LINKER_LIBS="-lSDL2 -lGL"
;; ;;
*) *)
error "operating system $OS has no configuration variants" error "operating system $OS has no configuration variants"
@@ -53,39 +49,25 @@ esac
# TASKS # TASKS
case "$TASK" in case "$TASK" in
# writes ttf fonts fron ./fonts/ to C compressed arrays in C source files
# builds static library from font arrays definitions
embed_fonts)
SRC_CPP=""
SRC_C=""
C_ARGS="-O0 -fpic"
WARN_C="-Wno-unused-const-variable"
CPP_ARGS="$C_ARGS"
STATIC_LIB_FILE="fonts_embedded.a"
TASK_SCRIPT=tasks/embed_fonts.sh
;;
# creates executable using profile info generated by build_profile # creates executable using profile info generated by build_profile
build_exec) build_exec)
# -flto applies more optimizations across object files # -flto applies more optimizations across object files
# -flto=auto is needed to multithreaded copilation # -flto=auto is needed to multithreaded copilation
# -fuse-linker-plugin is required to use static libs with lto, it strips away all # -fuse-linker-plugin is required to use static libs with lto, it strips away all
C_ARGS="-O2 -flto=auto -fuse-linker-plugin -static" C_ARGS="-O2 -flto=auto -fuse-linker-plugin"
#-fprofile-use -fprofile-prefix-path=$(realpath $OBJDIR)/objects #-fprofile-use -fprofile-prefix-path=$(realpath $OBJDIR)/objects
CPP_ARGS="$C_ARGS" CPP_ARGS="$C_ARGS"
LINKER_ARGS="$CPP_ARGS $LINKER_LIBS" LINKER_ARGS="$CPP_ARGS $LINKER_LIBS"
PRE_TASK_SCRIPT=tasks/pre_build.sh PRE_TASK_SCRIPT=
TASK_SCRIPT=cbuild/default_tasks/build_exec.sh TASK_SCRIPT=cbuild/default_tasks/build_exec.sh
DEPS_BUILD_TASK=build_static_lib
;; ;;
# creates executable with debug info and no optimizations # creates executable with debug info and no optimizations
build_exec_dbg) build_exec_dbg)
C_ARGS="-O0 -g" C_ARGS="-O0 -g -DDEBUG=1"
CPP_ARGS="$C_ARGS" CPP_ARGS="$C_ARGS"
LINKER_ARGS="$CPP_ARGS $LINKER_LIBS" LINKER_ARGS="$CPP_ARGS $LINKER_LIBS"
PRE_TASK_SCRIPT=tasks/pre_build.sh PRE_TASK_SCRIPT=
TASK_SCRIPT=cbuild/default_tasks/build_exec.sh TASK_SCRIPT=cbuild/default_tasks/build_exec.sh
DEPS_BUILD_TASK=build_static_lib_dbg
;; ;;
# executes $EXEC_FILE # executes $EXEC_FILE
exec) exec)
@@ -93,7 +75,7 @@ case "$TASK" in
;; ;;
# executes $EXEC_FILE with valgrind memory checker # executes $EXEC_FILE with valgrind memory checker
valgrind) valgrind)
VALGRIND_ARGS="-s --log-file=valgrind.log --read-var-info=yes --track-origins=yes --fullpath-after=$PROJECT/ --leak-check=full --show-leak-kinds=all" VALGRIND_ARGS="-s --log-file=valgrind.log --read-var-info=yes --track-origins=yes --fullpath-after=$(pwd) --leak-check=full --show-leak-kinds=all"
TASK_SCRIPT=cbuild/default_tasks/valgrind.sh TASK_SCRIPT=cbuild/default_tasks/valgrind.sh
;; ;;
# generates profiling info # generates profiling info
@@ -108,10 +90,9 @@ case "$TASK" in
C_ARGS="-O2 -flto=auto -fuse-linker-plugin -fprofile-generate -fprofile-prefix-path=$(realpath $OBJDIR)/objects" C_ARGS="-O2 -flto=auto -fuse-linker-plugin -fprofile-generate -fprofile-prefix-path=$(realpath $OBJDIR)/objects"
CPP_ARGS="$C_ARGS" CPP_ARGS="$C_ARGS"
LINKER_ARGS="$CPP_ARGS" LINKER_ARGS="$CPP_ARGS"
PRE_TASK_SCRIPT=tasks/pre_build.sh PRE_TASK_SCRIPT=
TASK_SCRIPT=cbuild/default_tasks/build_exec.sh TASK_SCRIPT=cbuild/default_tasks/build_exec.sh
POST_TASK_SCRIPT=cbuild/default_tasks/profile.sh POST_TASK_SCRIPT=cbuild/default_tasks/profile.sh
DEPS_BUILD_TASK=build_static_lib
;; ;;
# compiles program with -pg and runs it with gprof # compiles program with -pg and runs it with gprof
# uses gprof2dot python script to generate function call tree (pip install gprof2dot) # uses gprof2dot python script to generate function call tree (pip install gprof2dot)
@@ -122,10 +103,9 @@ case "$TASK" in
C_ARGS="-O2 -flto=auto -fuse-linker-plugin -pg" C_ARGS="-O2 -flto=auto -fuse-linker-plugin -pg"
CPP_ARGS="$C_ARGS" CPP_ARGS="$C_ARGS"
LINKER_ARGS="$CPP_ARGS" LINKER_ARGS="$CPP_ARGS"
PRE_TASK_SCRIPT=tasks/pre_build.sh PRE_TASK_SCRIPT=
TASK_SCRIPT=cbuild/default_tasks/build_exec.sh TASK_SCRIPT=cbuild/default_tasks/build_exec.sh
POST_TASK_SCRIPT=cbuild/default_tasks/gprof.sh POST_TASK_SCRIPT=cbuild/default_tasks/gprof.sh
DEPS_BUILD_TASK=build_static_lib
;; ;;
# compiles executable with sanitizers and executes it to find errors and warnings # compiles executable with sanitizers and executes it to find errors and warnings
sanitize) sanitize)
@@ -133,10 +113,9 @@ case "$TASK" in
C_ARGS="-O0 -g3 -fsanitize=undefined,address" C_ARGS="-O0 -g3 -fsanitize=undefined,address"
CPP_ARGS="$C_ARGS" CPP_ARGS="$C_ARGS"
LINKER_ARGS="$CPP_ARGS" LINKER_ARGS="$CPP_ARGS"
PRE_TASK_SCRIPT=tasks/pre_build.sh PRE_TASK_SCRIPT=
TASK_SCRIPT=cbuild/default_tasks/build_exec.sh TASK_SCRIPT=cbuild/default_tasks/build_exec.sh
POST_TASK_SCRIPT=cbuild/default_tasks/exec.sh POST_TASK_SCRIPT=cbuild/default_tasks/exec.sh
DEPS_BUILD_TASK=build_static_lib
;; ;;
# compiles program and runs it with callgrind (part of valgrind) # compiles program and runs it with callgrind (part of valgrind)
# uses gprof2dot python script to generate function call tree (pip install gprof2dot) # uses gprof2dot python script to generate function call tree (pip install gprof2dot)
@@ -148,17 +127,23 @@ case "$TASK" in
C_ARGS="-O2 -flto=auto -fuse-linker-plugin" C_ARGS="-O2 -flto=auto -fuse-linker-plugin"
CPP_ARGS="$C_ARGS" CPP_ARGS="$C_ARGS"
LINKER_ARGS="$CPP_ARGS" LINKER_ARGS="$CPP_ARGS"
PRE_TASK_SCRIPT=tasks/pre_build.sh PRE_TASK_SCRIPT=
TASK_SCRIPT=cbuild/default_tasks/build_exec.sh TASK_SCRIPT=cbuild/default_tasks/build_exec.sh
POST_TASK_SCRIPT=cbuild/default_tasks/callgrind.sh POST_TASK_SCRIPT=cbuild/default_tasks/callgrind.sh
;; ;;
# rebuilds specified dependencies
# EXAMPLE: `cbuild rebuild_dependencies=libexample1,fonts`
# 'all' can be specified to rebuild all dependencies
rebuild_dependencies)
TASK_SCRIPT=cbuild/default_tasks/rebuild_dependencies.sh
;;
# deletes generated files # deletes generated files
clean) clean)
TASK_SCRIPT=cbuild/default_tasks/clean.sh TASK_SCRIPT=cbuild/default_tasks/clean.sh
POST_TASK_SCRIPT=tasks/clean_additions.sh POST_TASK_SCRIPT=tasks/clean_additions.sh
;; ;;
# nothing to do # nothing to do
no_task) "" | no_task)
;; ;;
# unknown task # unknown task
*) *)

View File

@@ -2,8 +2,20 @@
namespace GraphC::GraphModel { namespace GraphC::GraphModel {
Attribute::Attribute(id_t _id, Node* _parent_node, Attribute::Type _type, std::string _title) Attribute::Attribute(id_t _id, Node* _parent_node, Attribute::Type _type, std::string _title)
: id(_id), parent_node(_parent_node), type(_type), title(_title) : type(_type), id(_id), parent_node(_parent_node), title(_title)
{} {}
Attribute::Attribute() : Attribute(id_t_invalid, nullptr, Attribute::Type::Static, "NULL_ATTR")
{}
const RBTree<id_t, Edge*>& Attribute::getIncomingEdges() const {
return incoming_edges;
}
const RBTree<id_t, Edge*>& Attribute::getOutgoingEdges() const {
return outgoing_edges;
}
} }

View File

@@ -3,7 +3,10 @@
namespace GraphC::GraphModel { namespace GraphC::GraphModel {
Edge::Edge(id_t _id, id_t _from_attr_id, id_t _to_attr_id) Edge::Edge(id_t _id, id_t _from_attr_id, id_t _to_attr_id)
: id(_id), from_attr_id(_from_attr_id), to_attr_id(_to_attr_id) : id(_id), to_attr_id(_to_attr_id), from_attr_id(_from_attr_id)
{}
Edge::Edge() : Edge(id_t_invalid, id_t_invalid, id_t_invalid)
{} {}
} }

View File

@@ -1,47 +1,168 @@
#include "GraphModel.hpp" #include "GraphModel.hpp"
#include "../UsefulException.hpp"
#include "../format.hpp"
namespace GraphC::GraphModel { namespace GraphC::GraphModel {
Graph::Graph() Graph::Graph()
{} {}
Graph::Graph(RBTree<id_t, Node>& _nodes, RBTree<id_t, Edge>& _edges) Graph::Graph(RBTree<id_t, Node>&& _nodes, RBTree<id_t, Edge>&& _edges)
: nodes(_nodes), edges(_edges) : nodes(_nodes), edges(_edges)
{} {}
RBTree<id_t, Node>& Graph::getNodes(){
const RBTree<id_t, Node>& Graph::getNodes() const {
return nodes; return nodes;
} }
RBTree<id_t, Edge>& Graph::getEdges(){ Node* Graph::createNode(Node& n){
Node* result = nullptr;
useful_assert(nodes.tryAdd(n.id, n, &result),
format("can't create node with id %i", n.id));
return result;
}
Node* Graph::createNode(Node&& n){
return createNode(n);
}
bool Graph::tryGetNode(id_t node_id, Node** result) const {
return nodes.tryGet(node_id, result);
}
void Graph::deleteNode(Node& n){
for(const auto& attr_p : n.attributes)
deleteAttribute(*attr_p.value);
useful_assert(nodes.tryDelete(n.id),
format("can't delete node with id %i", n.id));
}
void Graph::deleteNode(Node&& n){
deleteNode(n);
}
bool Graph::tryDeleteNode(id_t node_id){
Node* n = nullptr;
if(!nodes.tryGet(node_id, &n))
return false;
deleteNode(*n);
return true;
}
const RBTree<id_t, Edge>& Graph::getEdges() const {
return edges; return edges;
} }
Edge* Graph::createEdge(Attribute& from, Attribute& to){
id_t id = id_gen.getNext();
Edge* e = nullptr;
useful_assert(edges.tryAdd(id, Edge(id, from.id, to.id), &e),
format("can't create edge with id %i",id));
bool tryCreateEdge(Attribute& from, Attribute& to, Edge&& result); useful_assert(from.incoming_edges.tryAdd(id, e, nullptr),
bool tryGetEdge(id_t edge_id, Edge&& result); format("edge with id %i already exists in Attribute::incoming_edges", id));
bool tryDeleteEdge(id_t edge_id);
void Graph::deleteNode(id_t node_id){ useful_assert(to.outgoing_edges.tryAdd(id, e, nullptr),
if(nodes.erase(node_id) < 1) format("edge with id %i already exists in Attribute::outgoing_edges", id));
throw UsefulException("can't erase node with id"+toString_i64(node_id)); return e;
} }
void Graph::deleteEdge(id_t edge_id){ Edge* Graph::createEdge(Attribute&& from, Attribute&& to){
return createEdge(from, to);
}
bool Graph::tryCreateEdge(id_t from_attr_id, id_t to_attr_id, Edge** result){
Attribute* from;
if(!tryGetAttribute(from_attr_id, &from))
return false;
Attribute* to;
if(!tryGetAttribute(to_attr_id, &to))
return false;
Edge* e = createEdge(*from, *to);
if(result)
*result = e;
return true;
}
bool Graph::tryGetEdge(id_t edge_id, Edge** result) const {
return edges.tryGet(edge_id, result);
}
void Graph::deleteEdge(Edge& e){
Attribute* a = nullptr;
useful_assert(tryGetAttribute(e.from_attr_id, &a),
format("source attribute with id %i was not found in Graph::nodes", e.from_attr_id));
a->outgoing_edges.tryDelete(e.id);
useful_assert(tryGetAttribute(e.to_attr_id, &a),
format("destination attribute with id %i was not found in Graph::nodes", e.to_attr_id));
a->incoming_edges.tryDelete(e.id);
useful_assert(edges.tryDelete(e.id),
format("can't delete attribute with id %i", e.id));
}
void Graph::deleteEdge(Edge&& e){
deleteEdge(e);
}
bool Graph::tryDeleteEdge(id_t edge_id){
Edge* e;
if(!edges.tryGet(edge_id, &e))
return false;
deleteEdge(*e);
return true;
} }
bool Graph::tryGetAttribute(id_t attr_id, Attribute*& result){ const RBTree<id_t, Attribute>& Graph::getAttributes() const {
for(auto&& p_node : nodes){ return attributes;
auto&& node_attrs = p_node.second.getAttributes(); }
auto&& it = node_attrs.find(attr_id);
if(it != node_attrs.end()){ bool Graph::tryGetAttribute(id_t attr_id, Attribute** result) const {
result = &it->second; return attributes.tryGet(attr_id, result);
return true; }
}
Attribute* Graph::createAttribute(Attribute& a){
Attribute* result = nullptr;
useful_assert(attributes.tryAdd(a.id, a, &result),
format("can't create attribute with id %i to Graph", a.id));
useful_assert(a.parent_node->attributes.tryAdd(a.id, result, nullptr),
format("can't add attribute with id %i to Node", a.id));
return result;
}
Attribute* Graph::createAttribute(Attribute&& a){
return createAttribute(a);
}
void Graph::deleteAttribute(Attribute& a){
for(const auto& edge_p : a.incoming_edges){
useful_assert(edges.tryDelete(edge_p.value->id),
format("can't find edge with id %i", edge_p.value->id));
} }
return false; for(const auto& edge_p : a.outgoing_edges){
useful_assert(edges.tryDelete(edge_p.value->id),
format("can't find edge with id %i", edge_p.value->id));
}
useful_assert(attributes.tryDelete(a.id),
format("can't delete attribute with id %i", a.id));
}
void Graph::deleteAttribute(Attribute&& a){
deleteAttribute(a);
}
bool Graph::tryDeleteAttribute(id_t attr_id){
Attribute* a;
if(!attributes.tryGet(attr_id, &a))
return false;
deleteAttribute(*a);
return true;
} }
} }

View File

@@ -9,6 +9,7 @@
namespace GraphC::GraphModel { namespace GraphC::GraphModel {
typedef i32 id_t; typedef i32 id_t;
#define id_t_invalid ((id_t)-1)
class IdGenerator { class IdGenerator {
id_t next_id=1; id_t next_id=1;
@@ -17,39 +18,50 @@ public:
id_t getNext(); id_t getNext();
}; };
typedef struct Node Node; typedef class Node Node;
typedef class Attribute Attribute;
typedef struct Edge Edge; typedef struct Edge Edge;
typedef class Graph Graph;
class Attribute { class Attribute {
std::vector<std::shared_ptr<Edge>> incoming_edges; friend class Graph;
std::vector<std::shared_ptr<Edge>> outgoing_edges;
// pointers to instances in RBTree::edges
RBTree<id_t, Edge*> incoming_edges;
RBTree<id_t, Edge*> outgoing_edges;
public: public:
enum class Type { enum class Type {
Input, Output, Static Static, Input, Output
}; };
const Attribute::Type type; const Attribute::Type type;
const id_t id; const id_t id;
const Node* parent_node; Node *const parent_node;
std::string title; std::string title;
///@warning empty constructor for RBTree null-value node, do not use it
Attribute();
Attribute(id_t _id, Node* _parent_node, Attribute::Type _type, std::string _title); Attribute(id_t _id, Node* _parent_node, Attribute::Type _type, std::string _title);
const RBTree<id_t, Edge*>& getIncomingEdges() const;
const RBTree<id_t, Edge*>& getOutgoingEdges() const;
}; };
class Node { class Node {
RBTree<id_t, Attribute> attributes; friend class Graph;
// pointers to Attributes in Graph::attributes
RBTree<id_t, Attribute*> attributes;
public: public:
const id_t id; const id_t id;
std::string title; std::string title;
///@warning empty constructor for RBTree null-value node, do not use it
Node();
Node(id_t _id, std::string _title); Node(id_t _id, std::string _title);
RBTree<id_t, Attribute>& getAttributes(); const RBTree<id_t, Attribute*>& getAttributes() const;
bool tryCreateAttribute(IdGenerator id_gen, Attribute*& result);
bool tryGetAttribute(id_t attr_id, Attribute*& result);
bool tryDeleteAttribute(id_t attr_id);
}; };
struct Edge { struct Edge {
@@ -57,34 +69,63 @@ struct Edge {
const id_t to_attr_id; const id_t to_attr_id;
const id_t from_attr_id; const id_t from_attr_id;
///@warning empty constructor for RBTree null-value node, do not use it
Edge();
Edge(id_t _id, id_t _from_attr_id, id_t _to_attr_id); Edge(id_t _id, id_t _from_attr_id, id_t _to_attr_id);
}; };
class Graph { class Graph {
friend class Attribute;
friend class Node;
friend class Edge;
RBTree<id_t, Node> nodes; RBTree<id_t, Node> nodes;
RBTree<id_t, Edge> edges; RBTree<id_t, Edge> edges;
RBTree<id_t, Attribute> attributes;
public: public:
IdGenerator id_gen; IdGenerator id_gen;
Graph(); Graph();
Graph(RBTree<id_t, Node>& _nodes, RBTree<id_t, Edge>& _edges); Graph(RBTree<id_t, Node>&& _nodes, RBTree<id_t, Edge>&& _edges);
RBTree<id_t, Node>& getNodes();
bool tryCreateNode(std::string _title, Node*& result); const RBTree<id_t, Node>& getNodes() const;
bool tryGetNode(id_t node_id, Node*& result);
Node* createNode(Node& n);
Node* createNode(Node&& n);
///@param result nullable
bool tryGetNode(id_t node_id, Node** result) const;
void deleteNode(Node& n);
void deleteNode(Node&& n);
bool tryDeleteNode(id_t node_id); bool tryDeleteNode(id_t node_id);
RBTree<id_t, Edge>& getEdges();
bool tryCreateEdge(Attribute& from, Attribute& to, Edge*& result); const RBTree<id_t, Edge>& getEdges() const;
bool tryGetEdge(id_t edge_id, Edge*& result);
Edge* createEdge(Attribute& from, Attribute& to);
Edge* createEdge(Attribute&& from, Attribute&& to);
///@param result nullable
bool tryCreateEdge(id_t from_attr_id, id_t to_attr_id, Edge** result);
///@param result nullable
bool tryGetEdge(id_t edge_id, Edge** result) const;
void deleteEdge(Edge& e);
void deleteEdge(Edge&& e);
bool tryDeleteEdge(id_t edge_id); bool tryDeleteEdge(id_t edge_id);
bool tryGetAttribute(id_t attr_id, Attribute*& result);
const RBTree<id_t, Attribute>& getAttributes() const;
Attribute* createAttribute(Attribute& attr);
Attribute* createAttribute(Attribute&& attr);
///@param result nullable
bool tryGetAttribute(id_t attr_id, Attribute** result) const;
void deleteAttribute(Attribute& a);
void deleteAttribute(Attribute&& a);
bool tryDeleteAttribute(id_t attr_id);
}; };
} }

View File

@@ -6,4 +6,11 @@ Node::Node(id_t _id, std::string _title)
: id(_id), title(_title) : id(_id), title(_title)
{} {}
Node::Node() : Node(id_t_invalid, "NULL_NODE")
{}
const RBTree<id_t, Attribute*>& Node::getAttributes() const {
return attributes;
}
} }

View File

@@ -1,76 +1,117 @@
#pragma once #pragma once
#include "../../dependencies/kerep/src/base/base.h" #include <sstream>
#include <map> #include "UsefulException.hpp"
#include <memory>
// template<typename TKey, typename TVal> template<typename TKey, typename TVal>
typedef char* TKey;
typedef char* TVal;
class RBTree { class RBTree {
enum class Color { enum class Color {
Red, Black Red, Black
}; };
struct Node { // public version of struct Node
TKey key; // TreeIterator returns a reference to KVPair to hide all other stuff from users
public:
struct KVPair {
// members are in reverse order to minimize padding and make struct Node smaller
TVal value; TVal value;
TKey key;
KVPair(TKey& k, TVal& v) : value(v), key(k)
{}
protected:
// special constructor for tree leafs (null nodes)
// leaves key and value uninitialized
KVPair()
{}
};
private:
struct Node : public KVPair {
// stacks with KVPair::key if it is 32-bit or smaller
Color color; Color color;
Node* parent; Node* parent;
Node* left = nullptr; Node* left;
Node* right = nullptr; Node* right;
Node(TKey _key, TVal _val, Color _color, Node* _parent){ Node(TKey& _key, TVal& _val, Color _color, Node* _parent, Node* _left, Node* _right)
key = _key; : KVPair(_key, _val), color(_color), parent(_parent), left(_left), right(_right)
value = _val; {}
color = _color;
parent = _parent; Node() : color(Color::Black), parent(this), left(this), right(this)
{}
bool isLeaf() const {
return this == null_node_ptr;
} }
~Node(){ void deleteChildren(){
if(left != nullptr) if(isLeaf())
return;
if(!left->isLeaf()){
left->deleteChildren();
delete left; delete left;
if(right != nullptr) }
if(!right->isLeaf()){
right->deleteChildren();
delete right; delete right;
}
} }
inline Node* getSibling(){ /// if is leaf returns itself
if(parent == nullptr) Node* getMinChild() {
return nullptr; Node* n = this;
else if(parent->left == this) while(!n->left->isLeaf())
return parent->right; n = n->left;
else return parent->left; return n;
} }
inline Node* getGrandparent(){ /// if is leaf returns itself
if(parent == nullptr) Node* getMaxChild() {
return nullptr; Node* n = this;
else return parent->parent; while(!n->right->isLeaf())
} n = n->right;
return n;
inline Node* getUncle(){
if(parent == nullptr)
return nullptr;
return parent->getSibling();
} }
}; };
Node* root = nullptr;
static Node null_node;
static Node* null_node_ptr;
Node* root = &null_node;
///@returns null if root is null
Node* findParentForKey(TKey key) const {
Node* n = root;
Node* parent = null_node_ptr;
while(n != null_node_ptr){
parent = n;
if(key < n->key)
n = n->left;
else if(key > n->key)
n = n->right;
else return n->parent; // key == n->key
}
return parent;
}
void rotateLeft(Node* x){ void rotateLeft(Node* x){
// 1. get right child of x // 1. get right child of x
Node* y = x->right; Node* y = x->right;
// 2. move y to the position of x // 2. move y to the position of x
y->parent = x->parent; y->parent = x->parent;
if (x->parent != nullptr){ // x != root if(x->parent != null_node_ptr){ // x != root
if(x == x->parent->left) if(x == x->parent->left)
x->parent->left = y; x->parent->left = y;
else x->parent->right = y; else x->parent->right = y;
// TODO: maybe should set root to y?
} }
else root = y;
// 3. move y.left to x.right if it exists // 3. move y.left to x.right if it exists
x->right = y->left; x->right = y->left;
if (x->right != nullptr) if(x->right != null_node_ptr)
x->right->parent = x; x->right->parent = x;
// 4. move x to y.left // 4. move x to y.left
y->left = x; y->left = x;
@@ -82,180 +123,200 @@ class RBTree {
Node* y = x->left; Node* y = x->left;
// 2. move y up // 2. move y up
y->parent = x->parent; y->parent = x->parent;
if (x->parent != nullptr){ // x != root if(x->parent != null_node_ptr){ // x != root
if(x == x->parent->left) if(x == x->parent->left)
x->parent->left = y; x->parent->left = y;
else x->parent->right = y; else x->parent->right = y;
} }
else root = y;
// 3. move y.right to x.left if it exists // 3. move y.right to x.left if it exists
x->left = y->right; x->left = y->right;
if (x->left != nullptr) if(x->left != null_node_ptr)
x->left->parent = x; x->left->parent = x;
// 4. move x to y.right // 4. move x to y.right
y->right = x; y->right = x;
x->parent = y; x->parent = y;
} }
///@returns null if root is null
Node* findParentForKey(TKey key){
Node* n = root;
Node* parent = nullptr;
while(n != nullptr){
parent = n;
if(key < n->key)
n = n->left;
else if(key > n->key)
n = n->right;
else return nullptr; // key == n->key
}
return parent;
}
void fixupInsertion(Node* n){
// case 1: n is root -- root must be black
if (n->parent == nullptr){
n->color = Color::Black;
return;
}
// case 2: parent is black -- no requirements mismatch
if (n->parent->color == Color::Black)
return;
// case 3: parent and uncle are red -- red nodes must have black parents
Node* u = n->getUncle();
Node* g = n->getGrandparent();
if(u != nullptr && u->color == Color::Red){
n->parent->color = Color::Black;
u->color = Color::Black;
g->color = Color::Red;
fixupInsertion(g);
return;
}
// case 4: parent is red and uncle is black -- red nodes must have black parents
if ((n == n->parent->right) && (n->parent == g->left)) {
rotateLeft(n->parent);
n = n->left;
}
else if ((n == n->parent->left) && (n->parent == g->right)) {
rotateRight(n->parent);
n = n->right;
}
// case 5
n->parent->color = Color::Black;
g->color = Color::Red;
if ((n == n->parent->left) && (n->parent == g->left))
rotateRight(g);
else rotateLeft(g);
}
void fixupDeletion(Node* n){
// case 1
if(n->parent == nullptr)
return;
// case 2
Node* s = n->getSibling();
if(s->color == Color::Red){
n->parent->color = Color::Red;
s->color = Color::Black;
if (n == n->parent->left)
rotateLeft(n->parent);
else rotateRight(n->parent);
}
// case 3
if ((n->parent->color == Color::Black) &&
(s->color == Color::Black) &&
(s->left->color == Color::Black) &&
(s->right->color == Color::Black))
{
s->color = Color::Red;
fixupDeletion(n->parent);
return;
}
// case 4
else if ((n->parent->color == Color::Red) &&
(s->color == Color::Black) &&
(s->left->color == Color::Black) &&
(s->right->color == Color::Black))
{
s->color = Color::Red;
n->parent->color = Color::Black;
return;
}
// case 5
if(s->color == Color::Black) {
if ((n == n->parent->left) &&
(s->right->color == Color::Black) &&
(s->left->color == Color::Red))
{
s->color = Color::Red;
s->left->color = Color::Black;
rotateRight(s);
}
else if ((n == n->parent->right) &&
(s->left->color == Color::Black) &&
(s->right->color == Color::Red))
{
s->color = Color::Red;
s->right->color = Color::Black;
rotateLeft(s);
}
}
// case 6
s->color = n->parent->color;
n->parent->color = Color::Black;
if (n == n->parent->left) {
s->right->color = Color::Black;
rotateLeft(n->parent);
} else {
s->left->color = Color::Black;
rotateRight(n->parent);
}
}
void transplantNode(Node* old, Node* neww){ void transplantNode(Node* old, Node* neww){
neww->parent = old->parent; if(old->parent == null_node_ptr)
if(old->parent == nullptr)
root = neww; root = neww;
else if(old->parent->left == old) else if(old->parent->left == old)
old->parent->left = neww; old->parent->left = neww;
else old->parent->right = neww; else old->parent->right = neww;
if(neww != null_node_ptr)
neww->parent = old->parent;
} }
// n should be not null void fixupInsertion(Node* x){
Node* getMinimalChild(Node* n){ while(x != root && x->parent->color == Color::Red) {
while(n->left != nullptr) if(x->parent == x->parent->parent->left) {
n = n->left; Node* y = x->parent->parent->right;
return n; // uncle is red
if(y->color == Color::Red) {
x->parent->color = Color::Black;
y->color = Color::Black;
x->parent->parent->color = Color::Red;
x = x->parent->parent;
}
// uncle is black
else {
if(x == x->parent->right) {
// make x a left child
x = x->parent;
rotateLeft(x);
}
// recolor and rotate
x->parent->color = Color::Black;
x->parent->parent->color = Color::Red;
rotateRight(x->parent->parent);
}
}
// mirrored above code
else {
Node* y = x->parent->parent->left;
// uncle is red
if(y->color == Color::Red) {
x->parent->color = Color::Black;
y->color = Color::Black;
x->parent->parent->color = Color::Red;
x = x->parent->parent;
}
// uncle is black
else {
if(x == x->parent->left) {
// make x a right child
x = x->parent;
rotateRight(x);
}
// recolor and rotate
x->parent->color = Color::Black;
x->parent->parent->color = Color::Red;
rotateLeft(x->parent->parent);
}
}
}
root->color = Color::Black;
} }
// n should be not null void fixupDeletion(Node* x){
Node* getMaximalChild(Node* n){ while(x != root && x->color == Color::Black) {
while(n->right != nullptr) if(x == x->parent->left) {
n = n->right; Node* w = x->parent->right;
return n; if(w->color == Color::Red) {
w->color = Color::Black;
x->parent->color = Color::Red;
rotateLeft(x->parent);
w = x->parent->right;
}
if(w->left->color == Color::Black && w->right->color == Color::Black) {
w->color = Color::Red;
x = x->parent;
} else {
if(w->right->color == Color::Black) {
w->left->color = Color::Black;
w->color = Color::Red;
rotateRight(w);
w = x->parent->right;
}
w->color = x->parent->color;
x->parent->color = Color::Black;
w->right->color = Color::Black;
rotateLeft(x->parent);
x = root;
}
}
else {
Node* w = x->parent->left;
if(w->color == Color::Red) {
w->color = Color::Black;
x->parent->color = Color::Red;
rotateRight(x->parent);
w = x->parent->left;
}
if(w->right->color == Color::Black && w->left->color == Color::Black) {
w->color = Color::Red;
x = x->parent;
} else {
if(w->left->color == Color::Black) {
w->right->color = Color::Black;
w->color = Color::Red;
rotateLeft(w);
w = x->parent->left;
}
w->color = x->parent->color;
x->parent->color = Color::Black;
w->left->color = Color::Black;
rotateRight(x->parent);
x = root;
}
}
}
x->color = Color::Black;
} }
template<typename TreeIteratorValue>
struct TreeIterator {
Node* n;
TreeIterator(TreeIterator const& src){
n = src.n;
}
TreeIterator(Node* ptr){
n = ptr;
}
bool operator!=(TreeIterator const& other) const {
return n != other.n;
}
bool operator==(TreeIterator const& other) const {
return n == other.n;
}
TreeIteratorValue& operator*() const {
if(!n->isLeaf())
return *n;
else throw UsefulException("the caller has tried to get the value of the end of an iterator (null node value)");
}
void operator++() {
if(n->isLeaf())
return;
if(!n->right->isLeaf())
n = n->right->getMinChild();
else {
Node* p = n->parent;
while(n == p->right){
n = p;
p = p->parent;
}
n = p;
}
}
};
public: public:
using iterator = TreeIterator<KVPair>;
using const_iterator = TreeIterator<const KVPair>;
RBTree() {} RBTree() {}
~RBTree(){ ~RBTree(){
delete root; if(!root->isLeaf()){
root->deleteChildren();
delete root;
}
} }
/// @param resultPtr nullable
bool tryAdd(TKey key, TVal& value, TVal*& result){ bool tryAdd(TKey key, TVal& value, TVal** resultPtr){
if(root == nullptr){ if(root == null_node_ptr){
root = new Node(key, value, Color::Black, nullptr); root = new Node(key, value, Color::Black, null_node_ptr, null_node_ptr, null_node_ptr);
if(resultPtr)
*resultPtr = &root->value;
return true; return true;
} }
@@ -267,20 +328,35 @@ public:
else nodePtrPtr = &parent->right; else nodePtrPtr = &parent->right;
// if a child node already exists at this place, returns false // if a child node already exists at this place, returns false
if(*nodePtrPtr != nullptr) if(*nodePtrPtr != null_node_ptr){
if(resultPtr)
*resultPtr = nullptr;
return false; return false;
}
// places newNode to left or right of the parent // places newNode to left or right of the parent
Node* newNode = new Node(key, value, Color::Red, parent); Node* newNode = new Node(key, value, Color::Red, parent, null_node_ptr, null_node_ptr);
if(resultPtr)
*resultPtr = &newNode->value;
*nodePtrPtr = newNode; *nodePtrPtr = newNode;
// auto-balancing // auto-balancing
fixupInsertion(newNode); fixupInsertion(newNode);
return true; return true;
} }
bool trySet(TKey key, TVal& value){ /// @param resultPtr nullable
if(root == nullptr) bool tryAdd(TKey key, TVal&& value, TVal** resultPtr){
return tryAdd(key, value, resultPtr);
}
/// @param resultPtr nullable
bool trySet(TKey key, TVal& value, TVal** resultPtr){
if(root == null_node_ptr){
if(resultPtr)
*resultPtr = nullptr;
return false; return false;
}
Node* parent = findParentForKey(key); Node* parent = findParentForKey(key);
// ptr to parent->right or parent->left // ptr to parent->right or parent->left
@@ -290,17 +366,31 @@ public:
else nodePtrPtr = &parent->right; else nodePtrPtr = &parent->right;
// if a child node with the given key doesn't exist, returns false // if a child node with the given key doesn't exist, returns false
if(*nodePtrPtr == nullptr) if(*nodePtrPtr == null_node_ptr){
if(resultPtr)
*resultPtr = nullptr;
return false; return false;
}
// replaces the value of left or right child of the parent // replaces the value of left or right child of the parent
(*nodePtrPtr)->value = value; (*nodePtrPtr)->value = value;
if(resultPtr)
*resultPtr = &(*nodePtrPtr)->value;
return true; return true;
} }
void addOrSet(TKey key, TVal& value){ /// @param resultPtr nullable
if(root == nullptr){ bool trySet(TKey key, TVal&& value, TVal** resultPtr){
root = new Node(key, value, Color::Black, nullptr); return trySet(key, value, resultPtr);
}
/// @param resultPtr nullable
void addOrSet(TKey key, TVal& value, TVal** resultPtr){
if(root == null_node_ptr){
root = new Node(key, value, Color::Black, null_node_ptr, null_node_ptr, null_node_ptr);
if(resultPtr != nullptr)
*resultPtr = &root->value;
return; return;
} }
@@ -312,53 +402,74 @@ public:
else nodePtrPtr = &parent->right; else nodePtrPtr = &parent->right;
// if a child node already exists at this place, sets it's value // if a child node already exists at this place, sets it's value
if(*nodePtrPtr != nullptr){ if(*nodePtrPtr != null_node_ptr){
(*nodePtrPtr)->value = value; (*nodePtrPtr)->value = value;
if(resultPtr)
*resultPtr = &(*nodePtrPtr)->value;
return; return;
} }
// places newNode to left or right of the parent // places newNode to left or right of the parent
Node* newNode = new Node(key, value, Color::Red, parent); Node* newNode = new Node(key, value, Color::Red, parent, null_node_ptr, null_node_ptr);
if(resultPtr != nullptr)
*resultPtr = &newNode->value;
*nodePtrPtr = newNode; *nodePtrPtr = newNode;
// auto-balancing // auto-balancing
fixupInsertion(newNode); fixupInsertion(newNode);
} }
bool tryGet(TKey key, TVal** result){ /// @param resultPtr nullable
void addOrSet(TKey key, TVal&& value, TVal** resultPtr){
addOrSet(key, value, resultPtr);
}
bool tryGet(TKey key, TVal** resultPtr) const {
if(!resultPtr)
return false;
Node* parent = findParentForKey(key); Node* parent = findParentForKey(key);
Node* n; Node* n = null_node_ptr;
if(key < parent->key) if(parent == null_node_ptr)
n = root;
else if(key < parent->key)
n = parent->left; n = parent->left;
else n = parent->right; else n = parent->right;
// if there is no node with the given key // if there is no node with the given key
if(n == nullptr) if(n == null_node_ptr){
*resultPtr = nullptr;
return false; return false;
}
*result = &n->value; *resultPtr = &n->value;
return true; return true;
} }
bool tryDelete(TKey key){ bool tryDelete(TKey key){
Node* parent = findParentForKey(key); Node* parent = findParentForKey(key);
Node* n; Node* n = null_node_ptr;
if(key < parent->key) if(parent == null_node_ptr)
n = root;
else if(key < parent->key)
n = parent->left; n = parent->left;
else n = parent->right; else n = parent->right;
if(n == nullptr) // key not found // key not found
if(n == null_node_ptr){
return false; return false;
}
if(n->left == nullptr){ if(n->left == null_node_ptr){
transplantNode(n, n->right); transplantNode(n, n->right);
if(n->color == Color::Black) if(n->color == Color::Black && n->right != null_node_ptr)
fixupDeletion(n->right); fixupDeletion(n->right);
} }
else if(n->right == nullptr){ else if(n->right == null_node_ptr){
transplantNode(n, n->left); transplantNode(n, n->left);
if(n->color == Color::Black) if(n->color == Color::Black && n->left != null_node_ptr)
fixupDeletion(n->left); fixupDeletion(n->left);
} }
else { else {
Node* minNode = getMinimalChild(n->right); Node* minNode = n->right->getMinChild();
if(minNode != n->right){ if(minNode != n->right){
transplantNode(minNode, minNode->right); transplantNode(minNode, minNode->right);
minNode->right = n->right; minNode->right = n->right;
@@ -372,6 +483,79 @@ public:
fixupDeletion(minNode->right); fixupDeletion(minNode->right);
} }
delete n; // delete node without children
n->left = null_node_ptr;
n->right = null_node_ptr;
if(!n->isLeaf()){
n->deleteChildren();
delete n;
}
return true;
}
iterator begin(){
if(root == null_node_ptr)
return iterator(null_node_ptr);
return iterator(root->getMinChild());
}
iterator end(){
return iterator(null_node_ptr);
}
const_iterator begin() const {
if(root == null_node_ptr)
return const_iterator(null_node_ptr);
return const_iterator(root->getMinChild());
}
const_iterator end() const {
return const_iterator(null_node_ptr);
}
void _generateGraphVizCodeForChildren(std::stringstream& ss, Node* n) const {
if(n == null_node_ptr)
return;
if(n->color == Color::Red)
ss<<" \""<<n->key<<"\" [color=red]\n";
if(n->left == null_node_ptr){
ss<<" \""<<n->key<<"\" -> \"null\" [side=L]\n";
}
else {
ss<<" \""<<n->key<<"\" -> \""<<n->left->key<<"\" [side=L]\n";
_generateGraphVizCodeForChildren(ss, n->left);
}
if(n->right == null_node_ptr){
ss<<" \""<<n->key<<"\" -> \"null\" [side=R]\n";
}
else {
ss<<" \""<<n->key<<"\" -> \""<<n->right->key<<"\" [side=R]\n";
_generateGraphVizCodeForChildren(ss, n->right);
}
}
std::string generateGraphVizCode() const {
std::stringstream ss;
ss<<"digraph {\n"
" node [style=filled,color=gray];\n";
if(root == null_node_ptr)
ss<<" \"null\"\n";
else {
ss<<" \"null\" -> \""<<root->key<<"\"\n";
_generateGraphVizCodeForChildren(ss, root);
}
ss<<"}";
return ss.str();
} }
}; };
template<typename TKey, typename TVal>
typename RBTree<TKey, TVal>::Node RBTree<TKey, TVal>::null_node;
template<typename TKey, typename TVal>
typename RBTree<TKey, TVal>::Node* RBTree<TKey, TVal>::null_node_ptr = &RBTree<TKey, TVal>::null_node;

View File

@@ -1,12 +1,12 @@
#include "UsefulException.hpp" #include "UsefulException.hpp"
#include <sstream> #include <sstream>
UsefulException_::UsefulException_(std::string _message, std::string _file, std::string _func, int _line_n) UsefulException_::UsefulException_(const std::string& _message, const std::string& _file, const std::string& _func, int _line_n)
: message(_message), file(_file), function(_func), line_n(_line_n) : message(_message), file(_file), function(_func), line_n(_line_n)
{ {
std::stringstream ss; std::stringstream ss;
ss<<message<<'\n'; ss<<message<<'\n';
ss<<file<<':'<<_line_n<<" in "<<function; ss<<" at "<<file<<':'<<_line_n<<" in "<<function;
complete_text = ss.str(); complete_text = ss.str();
} }

View File

@@ -13,7 +13,9 @@ class UsefulException_ : public std::exception {
std::string complete_text; std::string complete_text;
public: public:
UsefulException_(std::string msg, std::string _file, std::string _func, int line_n); UsefulException_(const std::string& msg, const std::string& _file, const std::string& _func, int line_n);
virtual char const* what() const noexcept; virtual char const* what() const noexcept;
}; };
#define useful_assert(EXPR, ERRMSG) if(!EXPR) throw UsefulException(ERRMSG);

View File

@@ -4,7 +4,7 @@
#include "UsefulException.hpp" #include "UsefulException.hpp"
#include "../dependencies/kerep/src/base/base.h" #include "../dependencies/kerep/src/base/base.h"
std::string format(const std::string format_str, size_t args_count, ...){ std::string _format(const std::string& format_str, const size_t args_count, ...){
va_list vl; va_list vl;
va_start(vl, args_count); va_start(vl, args_count);
std::stringstream ss; std::stringstream ss;

View File

@@ -1,5 +1,7 @@
#pragma once #pragma once
#include <string> #include <string>
#include "../dependencies/kerep/src/base/std.h"
std::string format(const std::string format_str, size_t args_count, ...); std::string _format(const std::string& format_str, const size_t args_count, ...);
#define format(FORMAT_STR, ARGS...) _format(FORMAT_STR, count_args(ARGS) ,##ARGS)

179
src/gui/GraphEditor.cpp Normal file
View File

@@ -0,0 +1,179 @@
#include "gui.hpp"
#include <algorithm>
#include "GraphEditor.hpp"
namespace GraphC::gui {
void GraphEditor::drawNode(const GraphModel::Node& node){
ImNodes::BeginNode(node.id);
ImNodes::BeginNodeTitleBar();
ImGui::TextUnformatted(node.title.c_str());
ImNodes::EndNodeTitleBar();
for(auto& a_p : node.getAttributes()){
auto&& a = *a_p.value;
switch (a.type){
case GraphModel::Attribute::Type::Input:
ImNodes::BeginInputAttribute(a.id);
ImGui::Text("%s", a.title.c_str());
ImNodes::EndInputAttribute();
break;
case GraphModel::Attribute::Type::Output:
ImNodes::BeginOutputAttribute(a.id);
ImGui::Text("%s", a.title.c_str());
ImGui::Indent(40);
ImNodes::EndOutputAttribute();
break;
case GraphModel::Attribute::Type::Static:
ImNodes::BeginStaticAttribute(a.id);
ImGui::Text("%s", a.title.c_str());
ImNodes::EndStaticAttribute();
break;
default:
throw "Node::draw() invalid type";
break;
}
}
static const char* items[]={
"static","input", "output"
};
static int selected_item_i = 0;
if(ImGui::Button("add attribute"))
ImGui::OpenPopup("new_attribute_popup");
if(ImGui::BeginPopup("new_attribute_popup")){
ImGui::SeparatorText("New attribute properties");
static char buf[256];
ImGui::InputText("label", buf, sizeof(buf));
if(ImGui::Button("select type"))
ImGui::OpenPopup("select_type_popup");
ImGui::SameLine();
ImGui::TextUnformatted(items[selected_item_i]);
if(ImGui::BeginPopup("select_type_popup")){
for (int i = 0; i < IM_ARRAYSIZE(items); i++)
if (ImGui::Selectable(items[i]))
selected_item_i = i;
ImGui::EndPopup();
}
if(ImGui::Button("Create attribute")){
graph.createAttribute(GraphModel::Attribute(
graph.id_gen.getNext(),
const_cast<GraphModel::Node*>(&node),
(GraphModel::Attribute::Type)selected_item_i,
std::string(buf)));
std::cout<<"ATTRIBUTES:\n";
std::cout<<graph.getAttributes().generateGraphVizCode()<<std::endl;
ImGui::CloseCurrentPopup();
buf[0] = 0;
selected_item_i = 0;
}
ImGui::EndPopup();
}
ImNodes::EndNode();
}
GraphModel::Node* GraphEditor::CreateExampleNode(const std::string& node_title){
GraphModel::Node* n = graph.createNode(GraphModel::Node(graph.id_gen.getNext(), node_title));
graph.createAttribute(GraphModel::Attribute(graph.id_gen.getNext(), n, GraphModel::Attribute::Type::Input, "In"));
graph.createAttribute(GraphModel::Attribute(graph.id_gen.getNext(), n, GraphModel::Attribute::Type::Output, "Out"));
graph.createAttribute(GraphModel::Attribute(graph.id_gen.getNext(), n, GraphModel::Attribute::Type::Static, "Static"));
return n;
}
GraphEditor::GraphEditor(const std::string& _title)
: title(_title)
{
// CreateExampleNode("Node A");
// CreateExampleNode("Node B");
// CreateExampleNode("Node C");
// CreateExampleNode("Node D");
std::cout<<"NODES:\n";
std::cout<<graph.getNodes().generateGraphVizCode()<<std::endl;
std::cout<<"ATTRIBUTES:\n";
std::cout<<graph.getAttributes().generateGraphVizCode()<<std::endl;
}
void GraphEditor::show(){
editor_open = true;
}
void GraphEditor::hide(){
editor_open = false;
}
void GraphEditor::draw(){
if(!editor_open)
return;
ImGui::Begin(title.c_str(), &editor_open);
ImGui::SetWindowSizeMin(300,300);
if(ImGui::Button("create node")){
ImGui::OpenPopup("create_node_popup");
}
static char buf[256];
if(ImGui::BeginPopup("create_node_popup")){
ImGui::SeparatorText("New node properties");
ImGui::InputText("label", buf, sizeof(buf));
if(ImGui::Button("create node")){
graph.createNode(GraphModel::Node(graph.id_gen.getNext(), buf));
std::cout<<"NODES:\n";
std::cout<<graph.getNodes().generateGraphVizCode()<<std::endl;
buf[0] = 0;
ImGui::CloseCurrentPopup();
}
ImGui::EndPopup();
}
ImNodes::BeginNodeEditor();
// draw nodes
for(auto& n_p : graph.getNodes()){
drawNode(n_p.value);
}
// draw edges
for(auto& p : graph.getEdges()){
ImNodes::Link(p.value.id, p.value.to_attr_id, p.value.from_attr_id);
}
ImNodes::EndNodeEditor();
// handle edge creation
GraphModel::id_t from_attr_id, to_attr_id;
if (ImNodes::IsLinkCreated(&from_attr_id, &to_attr_id))
{
if(!graph.tryCreateEdge(from_attr_id, to_attr_id, nullptr)){
ImGui::End();
throw UsefulException(format("can't create edge from attribute %i to attribute %i", from_attr_id, to_attr_id));
}
std::cout<<"EDGES:\n";
std::cout<<graph.getEdges().generateGraphVizCode()<<std::endl;
}
// handle edge destruction
GraphModel::id_t edge_id;
if (ImNodes::IsLinkDestroyed(&edge_id))
{
if(!graph.tryDeleteEdge(edge_id)){
ImGui::End();
throw UsefulException(format("can't delete edge with id %i", edge_id));
}
std::cout<<"EDGES:\n";
std::cout<<graph.getEdges().generateGraphVizCode()<<std::endl;
}
ImGui::End();
}
}

View File

@@ -6,14 +6,17 @@
namespace GraphC::gui { namespace GraphC::gui {
class NodeEditor { class GraphEditor {
std::string title=nullptr; std::string title=nullptr;
bool editor_open=false; bool editor_open=false;
ImNodesContext* editor_context=nullptr; ImNodesContext* editor_context=nullptr;
std::shared_ptr<GraphModel::Graph> graph; GraphModel::Graph graph;
void drawNode(const GraphModel::Node& node);
GraphModel::Node* CreateExampleNode(const std::string& title);
public: public:
NodeEditor(std::string _title); GraphEditor(const std::string& _title);
void draw(); void draw();
void show(); void show();

View File

@@ -1,104 +0,0 @@
#include "gui.hpp"
#include <algorithm>
#include "NodeEditor.hpp"
namespace GraphC::gui {
void drawNode(GraphModel::Node& node){
ImNodes::BeginNode(node.id);
ImNodes::BeginNodeTitleBar();
ImGui::TextUnformatted(node.title.c_str());
ImNodes::EndNodeTitleBar();
for(GraphModel::Attribute& a : node.attributes)
{
switch (a.type)
{
case GraphModel::Attribute::Type::Input:
ImNodes::BeginInputAttribute(a.id);
ImGui::Text("%s", a.title.c_str());
ImNodes::EndInputAttribute();
break;
case GraphModel::Attribute::Type::Output:
ImNodes::BeginOutputAttribute(a.id);
ImGui::Text("%s", a.title.c_str());
ImGui::Indent(40);
ImNodes::EndOutputAttribute();
break;
case GraphModel::Attribute::Type::Static:
ImNodes::BeginStaticAttribute(a.id);
ImGui::Text("%s", a.title.c_str());
ImNodes::EndStaticAttribute();
break;
default:
throw "Node::draw() invalid type";
break;
}
}
ImNodes::EndNode();
}
GraphModel::Node CreateExampleNode(GraphModel::id_t* next_id, std::string title){
GraphModel::Node a = GraphModel::Node((*next_id)++, title);
a.attributes.push_back(GraphModel::Attribute((*next_id)++, &a, GraphModel::Attribute::Type::Input, "In"));
a.attributes.push_back(GraphModel::Attribute((*next_id)++, &a, GraphModel::Attribute::Type::Output, "Out"));
a.attributes.push_back(GraphModel::Attribute((*next_id)++, &a, GraphModel::Attribute::Type::Static, "Static"));
return a;
}
NodeEditor::NodeEditor(std::string _title) : title(_title) {
}
void NodeEditor::show(){
editor_open = true;
}
void NodeEditor::hide(){
editor_open = false;
}
void NodeEditor::draw(){
if(!editor_open)
return;
ImGui::Begin(title.c_str(), &editor_open);
ImGui::SetWindowSizeMin(300,300);
ImNodes::BeginNodeEditor();
// draw nodes
for(auto&& p : graph->getNodes()){
drawNode(p.second);
}
// draw edges
const auto& edges = graph->getEdges();
edges.empl = GraphModel::Edge();
for(auto&& p : edges){
ImNodes::Link(p.second.id, p.second.to_attr_id, p.second.from_attr_id);
}
ImNodes::EndNodeEditor();
// handle edge creation
GraphModel::id_t to_attr_id;
GraphModel::id_t from_attr_id;
if (ImNodes::IsLinkCreated(&from_attr_id, &to_attr_id))
{
graph->createEdge(graph->getEdge(from_attr_id), graph->getEdge(to_attr_id));
}
// handle edge destruction
GraphModel::id_t edge_id;
if (ImNodes::IsLinkDestroyed(&edge_id))
{
auto iter = std::find_if(edges.begin(), edges.end(),
[edge_id](const GraphModel::Edge& edge) -> bool {
return edge.id == edge_id;
});
assert(iter != edges.end());
edges.erase(iter);
}
ImGui::End();
}
}

View File

@@ -1,9 +1,9 @@
#include "exceptions.hpp" #include "exceptions.hpp"
#include "../../dependencies/SDL2/include/SDL.h" #include <SDL.h>
namespace GraphC::gui { namespace GraphC::gui {
SDLException_::SDLException_(std::string _file, std::string _func, int _line_n) SDLException_::SDLException_(const std::string& _file,const std::string& _func, int _line_n)
: UsefulException_(SDL_GetError(), _file, _func, _line_n) : UsefulException_(SDL_GetError(), _file, _func, _line_n)
{ {
SDL_ClearError(); SDL_ClearError();

View File

@@ -6,12 +6,11 @@ namespace GraphC::gui {
#define SDLException() SDLException_(__FILE__, __func__, __LINE__) #define SDLException() SDLException_(__FILE__, __func__, __LINE__)
class SDLException_ : UsefulException_ { class SDLException_ : public UsefulException_ {
public: public:
SDLException_(std::string _file, std::string _func, int line_n); SDLException_(const std::string& _file, const std::string& _func, int line_n);
}; };
#define SDL_TRY_ZERO(FUNC_CALL) if(FUNC_CALL != 0) throw SDLException(); #define SDL_TRY(EXPR) if(EXPR) throw SDLException();
#define SDL_TRY_ONE(FUNC_CALL) if(FUNC_CALL != 1) throw SDLException();
} }

View File

@@ -1,19 +1,33 @@
#include "gui.hpp" #include "gui.hpp"
#include "../resources.hpp"
namespace GraphC::gui::fonts { namespace GraphC::gui::fonts {
ImFont* ImFont_LoadFromFile(const char* file_path, f32 font_size, f32 dpi){ // select all glyphs from font
static const ImWchar glyph_ranges[] = {
0x0020, 0xFFFF, 0
};
ImFont* ImFont_LoadFromFile(const std::string& file_path, f32 font_size, f32 dpi){
ImGuiIO& io = ImGui::GetIO(); ImGuiIO& io = ImGui::GetIO();
font_size *= dpi; font_size *= dpi;
return io.Fonts->AddFontFromFileTTF(file_path, font_size); return io.Fonts->AddFontFromFileTTF(file_path.c_str(), font_size, nullptr, glyph_ranges);
} }
ImFont* _ImFont_LoadEmbedded(const void* data, int data_size, const char* font_name, f32 font_size, f32 dpi){ ImFont* ImFont_LoadEmbedded(const std::string& font_name, f32 font_size, f32 dpi){
ImGuiIO& io = ImGui::GetIO(); ImGuiIO& io = ImGui::GetIO();
font_size *= dpi; font_size *= dpi;
ImFontConfig font_cfg = ImFontConfig(); ImFontConfig font_cfg = ImFontConfig();
ksprintf(font_cfg.Name, IM_ARRAYSIZE(font_cfg.Name), "%s, %.0fpx", font_name, font_size); font_cfg.FontDataOwnedByAtlas = false;
return io.Fonts->AddFontFromMemoryCompressedTTF(data, data_size, font_size, &font_cfg); ksprintf(font_cfg.Name, IM_ARRAYSIZE(font_cfg.Name), "%s %ipx", font_name.c_str(), (i32)font_size);
const resources::EmbeddedResource** _res = nullptr;
if(!resources::getMap()->tryGet("fonts/" + font_name + ".ttf", &_res))
throw UsefulException(format("can't load embedded font '%s'", font_name.c_str()));
const resources::EmbeddedResource* res = *_res;
return io.Fonts->AddFontFromMemoryTTF((void*)(res->data), res->size,
font_size, &font_cfg, glyph_ranges);
} }
} }

View File

@@ -1,23 +1,12 @@
#pragma once #pragma once
#include "../../dependencies/imgui/imgui.h" #include "imgui.h"
#include "../generated/fonts_embedded.h" #include "../../dependencies/kerep/src/base/std.h"
namespace GraphC::gui::fonts { namespace GraphC::gui::fonts {
#define __CAT3(A,B,C) A##B##C ImFont* ImFont_LoadFromFile(const std::string& file_path, f32 font_size, f32 dpi);
#define embedded_font_data(FONT) __CAT3(font_,FONT,_compressed_data)
#define embedded_font_data_size(FONT) __CAT3(font_,FONT,_compressed_size)
#define embedded_font_name(FONT) #FONT
ImFont* ImFont_LoadEmbedded(const std::string& font_name, f32 font_size, f32 dpi);
ImFont* ImFont_LoadFromFile(const char* file_path, f32 font_size, f32 dpi);
#define ImFont_LoadEmbedded(FONT, FONT_SIZE, DPI) _ImFont_LoadEmbedded( \
embedded_font_data(FONT), \
embedded_font_data_size(FONT), \
embedded_font_name(FONT), \
FONT_SIZE, DPI)
ImFont* _ImFont_LoadEmbedded(const void* data, int data_size, const char* font_name, f32 font_size, f32 dpi);
} }

View File

@@ -1,6 +1,6 @@
#include "gui.hpp" #include "gui.hpp"
#include "../../dependencies/imgui/backends/imgui_impl_sdl2.h" #include "backends/imgui_impl_sdl2.h"
#include "../../dependencies/imgui/backends/imgui_impl_opengl3.h" #include "backends/imgui_impl_opengl3.h"
namespace GraphC::gui { namespace GraphC::gui {
@@ -9,23 +9,23 @@ f32 GUI::getDPI(){
SDL_GL_GetDrawableSize(sdl_window, &w, &h); SDL_GL_GetDrawableSize(sdl_window, &w, &h);
int sim_w=0, sim_h=0; int sim_w=0, sim_h=0;
SDL_GetWindowSize(sdl_window, &sim_w, &sim_h); SDL_GetWindowSize(sdl_window, &sim_w, &sim_h);
f32 wdpi=w/sim_w; f32 wdpi=(f32)w / sim_w;
f32 hdpi=h/sim_h; f32 hdpi=(f32)h / sim_h;
f32 dpi=SDL_sqrtf(wdpi*wdpi + hdpi*hdpi); f32 dpi=SDL_sqrtf(wdpi*wdpi + hdpi*hdpi);
return dpi; return dpi;
} }
void GUI::init(const char* window_title){ void GUI::init(const char* window_title){
SDL_TRY_ZERO(SDL_Init(SDL_INIT_VIDEO)); SDL_TRY(SDL_Init(SDL_INIT_VIDEO));
SDL_version v; SDL_version v;
SDL_GetVersion(&v); SDL_GetVersion(&v);
kprintf("SDL version: %u.%u.%u\n", v.major, v.minor, v.patch); kprintf("SDL version: %u.%u.%u\n", v.major, v.minor, v.patch);
// GL 3.0 + GLSL 130 // GL 3.0 + GLSL 130
const char* glsl_version = "#version 130"; const char* glsl_version = "#version 130";
SDL_TRY_ZERO( SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, 0)); SDL_TRY( SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, 0));
SDL_TRY_ZERO( SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE)); SDL_TRY( SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE));
SDL_TRY_ZERO( SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3)); SDL_TRY( SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3));
SDL_TRY_ZERO( SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0)); SDL_TRY( SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0));
// From 2.0.18: Enable native IME. // From 2.0.18: Enable native IME.
#ifdef SDL_HINT_IME_SHOW_UI #ifdef SDL_HINT_IME_SHOW_UI
@@ -33,9 +33,9 @@ void GUI::init(const char* window_title){
#endif #endif
// Create window with graphics context // Create window with graphics context
SDL_TRY_ZERO( SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1)); SDL_TRY( SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1));
SDL_TRY_ZERO( SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24)); SDL_TRY( SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24));
SDL_TRY_ZERO( SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8)); SDL_TRY( SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8));
SDL_WindowFlags window_flags = (SDL_WindowFlags)( SDL_WindowFlags window_flags = (SDL_WindowFlags)(
SDL_WINDOW_OPENGL | SDL_WINDOW_OPENGL |
SDL_WINDOW_RESIZABLE | SDL_WINDOW_RESIZABLE |
@@ -43,9 +43,13 @@ void GUI::init(const char* window_title){
sdl_window = SDL_CreateWindow(window_title, sdl_window = SDL_CreateWindow(window_title,
SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED,
1280, 720, window_flags); 1280, 720, window_flags);
if(sdl_window == nullptr)
throw SDLException();
gl_context = SDL_GL_CreateContext(sdl_window); gl_context = SDL_GL_CreateContext(sdl_window);
SDL_TRY_ZERO( SDL_GL_MakeCurrent(sdl_window, gl_context)); if(gl_context == nullptr)
SDL_TRY_ZERO( SDL_GL_SetSwapInterval(1)); // Enable vsync throw SDLException();
SDL_TRY( SDL_GL_MakeCurrent(sdl_window, gl_context));
SDL_TRY( SDL_GL_SetSwapInterval(1)); // Enable vsync
// Setup Dear ImGui context // Setup Dear ImGui context
IMGUI_CHECKVERSION(); IMGUI_CHECKVERSION();
@@ -69,14 +73,13 @@ void GUI::init(const char* window_title){
// Setup Dear ImGui style // Setup Dear ImGui style
ImGui::StyleColorsDark(); ImGui::StyleColorsDark();
f32 dpi = getDPI(); f32 dpi = getDPI();
io.FontDefault=fonts:: ImFont_LoadEmbedded(default_font, default_font_size, dpi); io.FontDefault = fonts::ImFont_LoadEmbedded(default_font, default_font_size, dpi);
fonts:: ImFont_LoadEmbedded(font_Cousine_Regular, default_font_size, dpi);
ImNodes::CreateContext(); ImNodes::CreateContext();
ImNodes::StyleColorsDark(); ImNodes::StyleColorsDark();
ImNodes::PushAttributeFlag(ImNodesAttributeFlags_EnableLinkDetachWithDragClick); ImNodes::PushAttributeFlag(ImNodesAttributeFlags_EnableLinkDetachWithDragClick);
node_editor=NodeEditor("node editor"); node_editor = new GraphEditor("node editor");
node_editor.show(); node_editor->show();
} }
// Wait, poll and handle events (inputs, window resize, etc.) // Wait, poll and handle events (inputs, window resize, etc.)
@@ -84,7 +87,7 @@ void GUI::poll_events(u16& frame_updates_requested, bool wait){
SDL_Event event; SDL_Event event;
if(wait){ if(wait){
// waits for first event in cpu-efficient way // waits for first event in cpu-efficient way
SDL_TRY_ONE(SDL_WaitEvent(&event)); SDL_TRY(SDL_WaitEvent(&event) != 1);
} }
// dont wait for event // dont wait for event
else if(!SDL_PollEvent(&event)) else if(!SDL_PollEvent(&event))
@@ -108,20 +111,58 @@ void GUI::poll_events(u16& frame_updates_requested, bool wait){
} while (SDL_PollEvent(&event)); // if there are more events, handles them } while (SDL_PollEvent(&event)); // if there are more events, handles them
} }
void ImGui_drawErrorWindow(bool* draw_error_window, std::string& msg){
ImGui::Begin("ERROR", draw_error_window);
ImGui::Text("%s", msg.c_str());
ImGui::End();
}
void GUI::draw_ui(){
static std::string error_message;
static bool draw_error_window = false;
try {
// Draw UI
if(draw_error_window)
ImGui_drawErrorWindow(&draw_error_window, error_message);
else {
draw_bg_window();
draw_debug_window();
node_editor->draw();
}
}
catch(const std::exception& e){
error_message = "Catched exception: " + std::string(e.what());
draw_error_window = true;
std::cerr<<error_message<<std::endl;
}
catch(const char* cstr){
error_message = "Catched error message (const char*): " + std::string(cstr);
draw_error_window = true;
std::cerr<<error_message<<std::endl;
}
catch(const std::string& str){
error_message = "Catched error message (std::string): " + str;
draw_error_window = true;
std::cerr<<error_message<<std::endl;
}
catch(...){
error_message = "Catched unknown error";
draw_error_window = true;
std::cerr<<error_message<<std::endl;
}
}
void GUI::draw_frame(){ void GUI::draw_frame(){
// Start the Dear ImGui frame // Start the Dear ImGui frame
ImGui_ImplOpenGL3_NewFrame(); ImGui_ImplOpenGL3_NewFrame();
ImGui_ImplSDL2_NewFrame(); ImGui_ImplSDL2_NewFrame();
ImGui::NewFrame(); ImGui::NewFrame();
ImGuiIO& io = ImGui::GetIO();
// Draw UI draw_ui();
draw_bg_window();
draw_debug_window(io, &main_loop_wait_for_input);
node_editor.draw();
// Rendering // Rendering
ImGui::Render(); ImGui::Render();
ImGuiIO& io = ImGui::GetIO();
glViewport(0, 0, (int)io.DisplaySize.x, (int)io.DisplaySize.y); glViewport(0, 0, (int)io.DisplaySize.x, (int)io.DisplaySize.y);
glClearColor(clear_color.x * clear_color.w, clear_color.y * clear_color.w, clear_color.z * clear_color.w, clear_color.w); glClearColor(clear_color.x * clear_color.w, clear_color.y * clear_color.w, clear_color.z * clear_color.w, clear_color.w);
glClear(GL_COLOR_BUFFER_BIT); glClear(GL_COLOR_BUFFER_BIT);
@@ -230,15 +271,46 @@ void GUI::draw_bg_window(){
ImGui::DockSpace(dockspace_id, ImVec2(0.0f, 0.0f), dockspace_flags); ImGui::DockSpace(dockspace_id, ImVec2(0.0f, 0.0f), dockspace_flags);
// MenuBar // MenuBar
//TODO if(ImGui::BeginMainMenuBar()){
if(ImGui::BeginMenu("test")){
if(ImGui::MenuItem("throw exception")){
ImGui::EndMenu();
ImGui::EndMainMenuBar();
ImGui::End();
throw UsefulException("example exception");
}
if(ImGui::MenuItem("throw const char*")){
ImGui::EndMenu();
ImGui::EndMainMenuBar();
ImGui::End();
throw "cptr";
}
if(ImGui::MenuItem("throw std::string")){
ImGui::EndMenu();
ImGui::EndMainMenuBar();
ImGui::End();
throw std::string("str");
}
if(ImGui::MenuItem("throw unknown")){
ImGui::EndMenu();
ImGui::EndMainMenuBar();
ImGui::End();
throw 111;
}
ImGui::EndMenu();
}
ImGui::EndMainMenuBar();
}
ImGui::End(); ImGui::End();
} }
void GUI::draw_debug_window(ImGuiIO& io, bool* main_loop_wait_for_input){ void GUI::draw_debug_window(){
ImGuiIO& io = ImGui::GetIO();
ImGui::Begin("Debug Options"); ImGui::Begin("Debug Options");
ImGui::ColorEdit3("clear_color", (float*)&clear_color); ImGui::ColorEdit3("clear_color", (float*)&clear_color);
ImGui::Checkbox("main_loop_wait_for_input", main_loop_wait_for_input); ImGui::Checkbox("main_loop_wait_for_input", &main_loop_wait_for_input);
ImGui::Text("Application average %.3f ms/frame (%.2f FPS)", 1000.0f / io.Framerate, io.Framerate); ImGui::Text("Application average %.3f ms/frame (%.2f FPS)", 1000.0f / io.Framerate, io.Framerate);
ImGui::Checkbox("Demo Window", &show_demo_window); ImGui::Checkbox("Demo Window", &show_demo_window);
ImGui::Checkbox("Metrics/Debug Window", &show_metrics_window); ImGui::Checkbox("Metrics/Debug Window", &show_metrics_window);

View File

@@ -1,14 +1,17 @@
#pragma once #pragma once
#include "../../dependencies/kerep/src/base/base.h" #include "../../dependencies/kerep/src/base/std.h"
#include "../../dependencies/SDL2/include/SDL.h" #include "../../dependencies/kerep/src/kprint/kprintf.h"
#include "../../dependencies/SDL2/include/SDL_opengl.h" #include <SDL.h>
#include "../../dependencies/imgui/imgui.h" #include <SDL_opengl.h>
#include "imgui.h"
#include "../format.hpp" #include "../format.hpp"
#include "NodeEditor.hpp" #include "GraphEditor.hpp"
#include "imgui_extensions.hpp" #include "imgui_extensions.hpp"
#include "fonts.hpp" #include "fonts.hpp"
#include "exceptions.hpp" #include "exceptions.hpp"
#include <iostream>
#include <string>
/// converts hex color to float vector /// converts hex color to float vector
#define RGBAHexToF(R8,G8,B8,A8) ImVec4(((u8)35)/255.0f, ((u8)35)/255.0f, ((u8)50)/255.0f, ((u8)255)/255.0f) #define RGBAHexToF(R8,G8,B8,A8) ImVec4(((u8)35)/255.0f, ((u8)35)/255.0f, ((u8)50)/255.0f, ((u8)255)/255.0f)
@@ -17,7 +20,7 @@
namespace GraphC::gui { namespace GraphC::gui {
#define default_font font_DroidSans #define default_font "DroidSans"
class GUI { class GUI {
public: public:
@@ -31,9 +34,9 @@ private:
bool main_loop_wait_for_input=true; bool main_loop_wait_for_input=true;
bool show_demo_window = false; bool show_demo_window = false;
bool show_metrics_window = false; bool show_metrics_window = false;
SDL_Window* sdl_window; SDL_Window* sdl_window = nullptr;
SDL_GLContext gl_context; SDL_GLContext gl_context = nullptr;
NodeEditor node_editor = NodeEditor("new editor"); GraphEditor* node_editor = nullptr;
public: public:
void init(const char* window_title); void init(const char* window_title);
@@ -45,7 +48,8 @@ private:
void destroy(); void destroy();
void poll_events(u16& frame_updates_requested, bool wait); void poll_events(u16& frame_updates_requested, bool wait);
void draw_frame(); void draw_frame();
void draw_debug_window(ImGuiIO &io, bool *main_loop_wait_for_input); void draw_ui();
void draw_debug_window();
void draw_bg_window(); void draw_bg_window();
}; };

View File

@@ -1,6 +1,6 @@
#pragma once #pragma once
#include "../../dependencies/imgui/imgui.h" #include "imgui.h"
namespace ImGui { namespace ImGui {

View File

@@ -1,10 +1,9 @@
#include "gui/gui.hpp"
#include <iostream> #include <iostream>
// SDL redefines main function for compatibility.
// SDL for some reason redefines main // THe following define disables this feature.
#ifdef main #define SDL_MAIN_HANDLED
#undef main #include "gui/gui.hpp"
#endif #include "resources.hpp"
int main(const int argc, const char* const* argv){ int main(const int argc, const char* const* argv){
if(setlocale(LC_CTYPE, "C.UTF-8")!=0) if(setlocale(LC_CTYPE, "C.UTF-8")!=0)
@@ -15,6 +14,8 @@ int main(const int argc, const char* const* argv){
kt_endInit(); kt_endInit();
try { try {
resources::init();
GraphC::gui::GUI gui; GraphC::gui::GUI gui;
gui.init("GraphC"); gui.init("GraphC");
gui.startAndWait(); gui.startAndWait();
@@ -23,16 +24,16 @@ int main(const int argc, const char* const* argv){
std::cerr<<"Catched exception: "<<e.what()<<std::endl; std::cerr<<"Catched exception: "<<e.what()<<std::endl;
return -1; return -1;
} }
catch(const char* msg){ catch(const char* cstr){
std::cerr<<"Catched exception message: "<<msg<<std::endl; std::cerr<<"Catched error message (const char*): "<<cstr<<std::endl;
return -1; return -1;
} }
catch(std::string& msg){ catch(const std::string& str){
std::cerr<<"Catched exception message: "<<msg<<std::endl; std::cerr<<"Catched error message (std::string): "<<str<<std::endl;
return -1; return -1;
} }
catch(...){ catch(...){
std::cerr<<"Catched unknown exception"<<std::endl; std::cerr<<"Catched unknown error"<<std::endl;
return -1; return -1;
} }

24
src/resources.cpp Normal file
View File

@@ -0,0 +1,24 @@
#include "gui/gui.hpp"
#include "resources.hpp"
#include "UsefulException.hpp"
namespace resources {
static RBTree<std::string, const EmbeddedResource*>* _resourceMap = nullptr;
void init(){
if(_resourceMap != nullptr)
throw UsefulException("resource has been initialized already");
_resourceMap = new RBTree<std::string, const EmbeddedResource*>();
for(int i = 0; i < EmbeddedResource_table_count; i++){
kprintf("loading resource '%s' (%lliK)\n",
EmbeddedResource_table[i].path, EmbeddedResource_table[i].size/1024);
_resourceMap->tryAdd(EmbeddedResource_table[i].path, &EmbeddedResource_table[i], nullptr);
}
}
const RBTree<std::string, const EmbeddedResource*>* getMap(){
return _resourceMap;
}
}

15
src/resources.hpp Normal file
View File

@@ -0,0 +1,15 @@
#pragma once
#include <string>
#include "RBTree.hpp"
namespace resources {
extern "C" {
#include "generated/resources.h"
}
void init();
const RBTree<std::string, const EmbeddedResource*>* getMap();
}

5
tasks/clean_additions.sh Normal file → Executable file
View File

@@ -1,4 +1,3 @@
#!/usr/bin/bash #!/usr/bin/env bash
try_delete_dir_or_file fonts/generated try_delete_dir_or_file src/generated
try_delete_dir_or_file libs/fonts_embedded.a

View File

@@ -1,40 +0,0 @@
#!/bin/bash
cd fonts
SRC_C=""
HEADER="generated/fonts_embedded.h"
function append_def_to_header {
local c_src_file=$1
local c_var_name=$2
local header_file=$3
echo "#define $c_var_name $(echo $c_var_name | sed 's/font_//')" >> "$header_file"
sed '3!d;' $c_src_file | sed 's/const/static const/' >> "$header_file"
sed '4!d;' $c_src_file | sed 's/const/extern const/' | sed 's/\[.*/[];/' >> "$header_file"
}
rm -rf generated
mkdir generated
mkdir generated/src
$CMP_CPP binary_to_compressed_c.cpp -o generated/binary_to_compressed_c.exe
for ttf_file in $(ls *.ttf); do
c_var_name="font_$(basename $ttf_file .ttf | tr ' ' '_' | tr '-' '_')"
c_src_file="generated/src/$c_var_name.c"
myprint "$ttf_file : $c_var_name > c_src_file"
generated/binary_to_compressed_c.exe -nostatic "$ttf_file" "$c_var_name" > "$c_src_file"
SRC_C="$SRC_C fonts/$c_src_file"
append_def_to_header "$c_src_file" "$c_var_name" "$HEADER"
done
cd ..
mkdir -p src/generated/
cp -v fonts/$HEADER src/$HEADER
myprint "${GREEN}font arrays external definitions have been written to ${CYAN}src/$HEADER"
# delete old objects
clean_dir "$OBJDIR/objects"
# compiling
compile_c "$C_ARGS" "$SRC_C"
pack_static_lib "$STATIC_LIB_FILE"
# moving lib file
mv "$OUTDIR/$STATIC_LIB_FILE" libs/
mkdir -p "libs"
myprint "$OUTDIR/$STATIC_LIB_FILE -> libs/"

View File

@@ -1,22 +0,0 @@
#!/bin/bash
# compile fonts
if [ ! -f libs/fonts_embedded.a ]; then
if ! make embed_fonts; then
exit 1
fi
fi
set -x
# copy precompiled static libs to objects
static_libs=$(find "libs/" -name '*.a')
[[ -n "$static_libs" ]] && cp $static_libs "$OBJDIR/libs/"
# copy precompiled shared libs to outdir
if [ "$OS" == 'WINDOWS' ]; then
dynamic_libs=$(find "libs/" -name '*.dll')
else
dynamic_libs=$(find "libs/" -name '*.so')
fi
[[ -n "$dynamic_libs" ]] && cp $dynamic_libs "$OUTDIR/"
set +x