sfttech / nyan Goto Github PK
View Code? Open in Web Editor NEWTypesafe hierarchical key-value database with inheritance and dynamic patching :smiley_cat:
License: Other
Typesafe hierarchical key-value database with inheritance and dynamic patching :smiley_cat:
License: Other
Hey I want to use this.
I am making a game engine in C++17, and I need a way of dynamically interacting with objects. I'm considering adding scripting in Common Lisp but I'd like a C++ API (perhaps with CFFI support)
Is there any way I can help? I started on a Query system in https://github.com/kennymalac/game/blob/master/space.cpp
I'm still unsure of some details but I figure I'd want the Query system in my engine to be pretty similar to what would be used in the modding API.
The file
member type should store its value as a filename relative to the nyan file it was defined in.
This would allow that a content definition (sounds.nyan
) file can just reference to ./splortsch.opus
even though both are in content/awesomemod/assets/resources/
.
Somehow, a new_parents
vector is created in a Transaction
even there are no new parents for an object.
Find the bug and prevent that new_parents
is added to the changed
-map when there are no new parents.
### p tx
$19 = {
error = {
_M_exception_object = 0x0
},
valid = true,
at = 0,
states = std::vector of length 1, capacity 1 = {
std::tuple containing = {
[1] = std::shared_ptr (count 5, weak 1) 0x645ea0,
[2] = std::shared_ptr (count 1, weak 0) 0x644960,
[3] = {
changes = std::unordered_map with 1 element = {
["test.First"] = {
new_parents = std::vector of length 0, capacity 0
}
}
}
}
}
}
There aren't any references to buildings in the mods in the examples and I can't figure it out. Does the engine just add all buildings it reads in a mod file automatically or do you need to patch villagers to allow them to create them or something?
Currently all sets can be initialized as empty which is not always desirable. It should be possible to require a minimum number of elements that have to be defined by the non-abstract set. This could proof useful for cases where at least one object is expected to be present. For example, a set is supposed to contain a number of animations of which the engine selects one randomly. If the set is initiliazed with no animations present, the mechanic doesn't work.
Possible solution:
AnimatedAbility(Ability):
animations : set(Animation, 1)
The 1
denotes that at least one object is required on the set's initilization. Also set(Animation, 0)
would be the same as set(Animation)
. The requirement would only be relevant for the initilization. Patches could still make the set be empty.
These are already described in the specification, but not implemented yet.
They are necessary when an object inherits from multiple parents and name conflicts occur.
The name qualifications can explicitly designate which object the referenced member is linked to.
That means we can't just store the member name in an object, but instead store a tuple of (originating_object, member_name)
as the member identifier. The originating_object
is resolved at load time to the object that initially defined the member.
using ubuntu 16
cloned and got this error.
not sure if this is an issue with my system or something else.
[ 1%] [flex] Generating scanner with flex 2.6.0
Scanning dependencies of target nyan
[ 3%] Building CXX object nyan/CMakeFiles/nyan.dir/basic_type.cpp.o
[ 7%] Building CXX object nyan/CMakeFiles/nyan.dir/change_tracker.cpp.o
[ 7%] Building CXX object nyan/CMakeFiles/nyan.dir/ast.cpp.o
[ 9%] Building CXX object nyan/CMakeFiles/nyan.dir/curve.cpp.o
[ 11%] Building CXX object nyan/CMakeFiles/nyan.dir/database.cpp.o
[ 13%] Building CXX object nyan/CMakeFiles/nyan.dir/c3.cpp.o
[ 15%] Building CXX object nyan/CMakeFiles/nyan.dir/config.cpp.o
[ 17%] Building CXX object nyan/CMakeFiles/nyan.dir/datastructure/orderedset.cpp.o
[ 19%] Building CXX object nyan/CMakeFiles/nyan.dir/error.cpp.o
In file included from /home/games/nyan/nyan/datastructure/orderedset.cpp:3:0:
/home/kirk/games/nyan/nyan/datastructure/orderedset.h:9:15: error: expected ‘{’ before ‘::’ token
namespace nyan::datastructure {
^
/home/games/nyan/nyan/datastructure/orderedset.h:9:17: error: ‘datastructure’ in namespace ‘::’ does not name a type
namespace nyan::datastructure {
^
/home/games/nyan/nyan/datastructure/orderedset.cpp:5:15: error: expected ‘{’ before ‘::’ token
namespace nyan::datastructure {
^
/home/games/nyan/nyan/datastructure/orderedset.cpp:5:17: error: ‘datastructure’ in namespace ‘::’ does not name a type
namespace nyan::datastructure {
^
/home/games/nyan/nyan/datastructure/orderedset.cpp:8:1: error: expected ‘}’ at end of input
} // namespace nyan::datastructure
^
/home/games/nyan/nyan/datastructure/orderedset.cpp:8:1: error: expected ‘}’ at end of input
nyan/CMakeFiles/nyan.dir/build.make:237: recipe for target 'nyan/CMakeFiles/nyan.dir/datastructure/orderedset.cpp.o' failed
make[2]: *** [nyan/CMakeFiles/nyan.dir/datastructure/orderedset.cpp.o] Error 1
make[2]: *** Waiting for unfinished jobs....
CMakeFiles/Makefile2:986: recipe for target 'nyan/CMakeFiles/nyan.dir/all' failed
make[1]: *** [nyan/CMakeFiles/nyan.dir/all] Error 2
Makefile:140: recipe for target 'all' failed
make: *** [all] Error 2
It should be possible to declare types as optional(typename)
which then allows values not to be set.
Currently, the specification states that whenever a nyan-object is used as value in another nyan-object, all the members must have an assigned value.
With this feature, members of the optional(othertype)
type have a None
value by default. They can be assigned None
or a value of type othertype
.
None
becomes a keyword.
When member qualification is required, aliases could be specified to avoid writing the full origin object name.
Parent name aliases are only valid locally (i.e. whenever accessing this object's scope) and are not passed down the inheritance hierary.
ParentName():
lol : int
ObjName(ParentName as PN):
PN.lol = 1337
ChildObject(ObjName):
ParentName.lol = 42
In ChildObject
the alias is no longer valid.
I can't come up with a good syntax for an alias definition in the scope of this object.
Maybe something like this (but that can be confused with the inher-add synatax:
[inherdef](parentdef)[aliasdef]:
ChildObject(ObjName)[ParentName as PN]:
PN.lol = 42
We could add some means of documentation format (like the python docstrings).
That would allow generation of API pages, so mod developers can use this as reference instead of looking at the nyan
files.
Commas between a collection elements and parents are not needed in order for the nyan to be interpreted. So they could be optional.
LeggedCombineCitadel(Building, Unit):
hp = 1000
creates = {OverwatchSoldier, Strider}
LeggedCombineCitadel(Building, Unit):
hp = 1000
creates = { OverwatchSoldier Strider }
LeggedCombineCitadel ( Building Unit ) :
hp = 1000
creates = { OverwatchSoldier Strider }
It looks weird but why not...
We have thought about amending the nyan language spec to mitigate runtime errors:
optional(..)
type member has the None value assigned, all relative operations (+=
, *=
, etc.) on the value will have the result None
.inf
value for the int
/float
types can only be assigned -> Relative operation using inf
as the operand will be disallowed.inf *= 0
will cause a RuntimeError
Allow to use objects and namespaces only if they were imported properly.
Currently all objects are available, because no check for imports is performed.
The file contents loaded from util::read_file
can be used to optimize the storage requirements for Tokens (especially value
) using std::string_view
instead of std::string
.
With the amount of upcoming changes we probably should add a .travis.yml as a temporary solution (as long as our own solutions aren't ready) to check if nyan is building on MacOSX. In addition I would also propose to use appveyor (and integrate it next to Kevin and Travis) to check PRs for win32/64.
For sure this is not the cleanest solution, but it is better than not having any CI for both platforms and fight against broken builds.
Update: With Github Workflows and Actions we could get away from Travis and Appveyor also here (SFTtech/openage#1196).
The build fails on macOS:
[100%] Linking CXX executable nyancat
Undefined symbols for architecture x86_64:
"nyan::Number<long long>::apply_value(nyan::Value const&, nyan::nyan_op)", referenced from:
vtable for nyan::Number<long long> in nyan_tool.cpp.o
"nyan::Number<long long>::allowed_operations(nyan::Type const&) const", referenced from:
vtable for nyan::Number<long long> in nyan_tool.cpp.o
"nyan::Number<long long>::str() const", referenced from:
vtable for nyan::Number<long long> in nyan_tool.cpp.o
"nyan::Number<long long>::copy() const", referenced from:
vtable for nyan::Number<long long> in nyan_tool.cpp.o
"nyan::Number<long long>::hash() const", referenced from:
vtable for nyan::Number<long long> in nyan_tool.cpp.o
"nyan::Number<long long>::repr() const", referenced from:
vtable for nyan::Number<long long> in nyan_tool.cpp.o
"nyan::Number<long long>::equals(nyan::Value const&) const", referenced from:
vtable for nyan::Number<long long> in nyan_tool.cpp.o
ld: symbol(s) not found for architecture x86_64
clang: error: linker command failed with exit code 1 (use -v to see invocation)
make[2]: *** [nyan/nyancat] Error 1
make[1]: *** [nyan/CMakeFiles/nyancat.dir/all] Error 2
make: *** [all] Error 2
nyan
needs a Python API in order to be used from Python code, like the openage-converter.
Best to use Cython. It can be done with the traditional Cython approach by just wrapping the C++-code with pyx
and pxd
files.
Using openage's Python interface is probably overkill (no need to call back and forth easily, translate exceptions from Py to C++, ...).
nyan object members and sets with nyan object set types only allow non-abstract objects by default. This limitation is meant to ensure that when retrieving the object, all members are guaranteed to have values assigned. Costly checking for an uninitialized member is not necessary then.
However, it sometimes makes sense to still reference abstract objects, e.g. when the object reference is used for a type check where the member values are not needed. For this case we can use a type modifier abstract(type)
. For containers, this feature can be used in combination with the hierarchy_overwrite
feature (see #37) to add and replace objects in an inheritance hierarchy.
# Abstract object
OtherObject():
member : float
# Non-abstract child
ChildObject(OtherObject):
member = 5.0f
SomeObject():
# Default: Allows only non-abstract objects
member_set : set(OtherObject) = {ChildObject}
# With abstract modifier: Allows both non-abstract and abstract objects
member_set_abs : set(abstract(OtherObject)) = {OtherObject, ChildObject}
# also possible: non-container member with an abstract value
member_abs : abstract(OtherObject) = OtherObject
# This object is still abstract!
StillAbstract():
member_abs : abstract(OtherObject)
NoLongerAbstract(StillAbstract):
member_abs = OtherObject
I'm writing a very stripped down version of the aoe2 engine in javascript for some purposes, but I don't want to write a parser just to use nyan files :'( If someone wrote a nyan -> json or something, that would be awesome :D
For the forum and other platforms it would be nice to add nyan lang to highlight.js (Syntax highlighting for the Web) so nyan files are easier to read for example.
The project can be found on github as well:
https://github.com/highlightjs/highlight.js
Here is an example for lua.
According to the specification, nyan objects "remain abstract until all members have values" ("A nyan::Object
is "abstract" iff it contains at least one undefined member"). Some of the openage API objects would require objects to be abstract that contain no members. These objects are often used as generalization objects that should be inherited from, but not be used on their own.
Examples : GameEntityType
, DiplomaticStance
, TradeRoute
, DropoffType
Proposed solution: Use the same idea as in #58 and introduce parameters for sets. In this case the definition could look like this: set(typename, only_children=True)
For single references, use a new child
container type: child(typename)
The value collections need a items<T>()
function which returns an iterator dynamically casts each element to T
!
The operator *
of the iterator class must do the cast.
Currently, it's only allowed for a inheritance hierarchy of patches to define a patch target once.
This restriction can be softened a bit by allowing the patch target again, but then the target must be exactly the same.
It could even be narrowed down to restrict the refined (child) patch to a child of the original patch target.
Example:
Archer(Unit):
firerate : float = 0
Crossbowman(Archer)
firerate = 12
Longbowman(Archer):
firerate = 10
ThumbRing(Technology):
ThumbRing1<Longbowman>():
firerate *= 1.2
ThumbRing2<Crossbowman>():
firerate *= 1.2
patches = {ThumbRing1, ThumbRing2}
This is the correct solution so far, am I wrong ?
If you try to do this:
ThumbRing<Archer>(Technology):
firerate *= 1.2
Then the stack of calculation would be:
firerate = 0
firerate *= 1.2
firerate = 10
V
firerate = 10
However there could be something like defer which would move the operation to the end:
ThumbRing<Archer>(Technology):
firerate defer *= 1.2
And then there would be two stucks:
firerate = 0
defer firerate *= 1.2
firerate = 10
V
firerate = 0
firerate = 10
firerate *= 1.2
V
firerate = 10
firerate *= 1.2
V
firerate = 12
The keyframe storage lookup requires log(n)
steps at maximum when there are n
keyframes stored.
We could speed that up by looking more at the "end", because that is the time accesses are more likely (because that is where the game time of the engine is at).
nyan could provide a lsp server for completion and formatting in editors that support lsp.
Goal
The number types int
and float
should be compatible to each other, i.e. an integer value can be assigned/added/subtracted... to a float member and vice versa.
How To
If two number operands in a operation have different number types, the type result of the result should always be the left operands type:
int OP float
=> int
float OP int
=> float
A():
member : int = 5
B(A):
# A.member == floor(7.5) == 7
member *= 1.5
C<B>():
# B.member == 2.25
# A.member == floor(2.25 * 5) == floor(11.25) == 11
member *= 1.5
D(B):
# B.member == 4.5
# A.member == floor(4.5 * 5) == floor(22.5) == 22
member *= 3
English translation below.
Habe mal meine Gedanken während des Lesens der Spec runtergeschrieben. Sind linear entstanden, also es war noch nicht für jeden Kommentar alle Information vorhanden. Mehr so ein Gedankendump. :)
MoveAbility
wird noch nicht mit instant: bool
definiert sein, weil die Engineentwickler nicht daran denken, dass es mal gebraucht wird. Wenn jetzt bereits existierende Units teleportieren können sollen, muss man eine MoveAbilityInstant
Gschichte definieren, die nur instant: bool
enthält. Wenn eine bereits existierende Einheit jetzt teleportieren können soll, muss man sie erst für MoveAbilityInstant instantiieren, damit man sie für Teleport instantiieren kann.English translation of my braindump (wrote it in a linear fashing while reading through the spec):
patches
and objects
?MoveAbility
won’t be intitially defined with the instant: bool
field, because developers won’t think about it being needed later. If one now wants existing units to be able to implement Teleport
, one has to implement something like MoveAbilityInstant
which only contains the field instant: bool
; the previously mentioned unit will now instantiate MoveAbilityInstant and then TeleportAdd support for __init__.nyan
files that behave like Python's __init__.py
. When a folder is imported, this file is loaded and its definitions are loaded.
The definitons are handled just like a regular nyan
file.
Definitions in /rofl.nyan
are loaded exactly the same way as the same definitions in /rofl/__init__.py
.
Nyan will abort loading if both a /name.nyan
and /name/...
folder are a candidate for loading.
The __init__.nyan
is designed for imports (and alias definitions), that redirect to declarations of other nyan files in the same (or a sub-)folder.
In order to prevent compatibility problems in the future, we should require a nyan
version number in each nyan file.
Proposal:
# blabla copyright whatnot
# then the version specification
!format nyan 1.0
# blablabla
Object():
...
For openage dicts will be very useful in order to deal with what in the engine are called ResourceBundle
.
In nyan creating a set of something like ResourceAmount would be very verbose, but with dict(Resource, float)
a collection of resources can encoded very nicely:
Unit():
cost : dict(Resource, float)
Knight(Unit):
cost = {Food: 70, Gold: 75}
However, there is a need to be able to access the dict values separately. I would suggest something like that:
CheaperKnights<Knight>():
cost[Gold] -= 20
Sidenote: Time (training time) could also be modeled as a Resource and be included in the cost
I'm getting the following error when attempting to build nyan:
build [master]$ cmake ..
▄▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▄
█░░▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒░░░█
█░▒▒▒▒▒▒▒▒▒▒▄▀▀▄▒▒▒░░█▄▀▀▄
▄▄ █░▒▒▒▒▒▒▒▒▒▒█▓▓▓▀▄▄▄▄▀▓▓▓█ ▄ ▄▄ ▄ ▄ ▄▄▄ ▄ ▄▄
█▓▓█▄▄█░▒▒▒▒▒▒▒▒▒▄▀▓▓▓▓▓▓▓▓▓▓▓▓▀▄ █▀ █ ▀▄ ▄▀ ▀ █ █▀ █
▀▄▄▓▓█░▒▒▒▒▒▒▒▒▒█▓▓▓▄█▓▓▓▄▓▄█▓▓█ █ █ █▄█ ▄▀▀▀█ █ █
▀▀█░▒▒▒▒▒▒▒▒▒█▓▒▒▓▄▓▓▄▓▓▄▓▒▒█ █ █ ▀█ ▀▄▄▀█ █ █
▄█░░▒▒▒▒▒▒▒▒▒▀▄▓▓▀▀▀▀▀▀▀▓▄▀ ▄▀
▄▀▓▀█▄▄▄▄▄▄▄▄▄▄▄▄██████▀█▀▀ ▀▀
█▄▄▀ █▄▄▀ █▄▄▀ ▀▄▄█
For information about building, look at [doc/building.md].
If you encounter problems you can't fix yourself,
report problems at https://github.com/SFTtech/nyan
Contact: #sfttech:matrix.org
-- Could NOT find Doxygen (missing: DOXYGEN_EXECUTABLE)
CMake Warning at buildsystem/doxygen.cmake:50 (message):
doxygen couldn't be found, you won't be able to generate docs
Call Stack (most recent call first):
CMakeLists.txt:78 (doxygen_configure)
-- registering nyan in the cmake user package registry (~/.cmake/package/nyan)...
project | nyan
version | 1.0
compiler | AppleClang 9.0.0.9000039
build type | Debug
cxxflags | -Wall -Wextra -pedantic
build type flags | -O0 -ggdb3
build dir | /Users/user/dev/sandbox/nyan/build
install prefix | /usr/local
CMake Error: The following variables are used in this project, but they are set to NOTFOUND.
Please set them or make sure they are set and tested correctly in the CMake files:
/Users/user/dev/sandbox/nyan/nyan/FLEX_INCLUDE_DIR
used as include directory in directory /Users/user/dev/sandbox/nyan/nyan
-- Configuring incomplete, errors occurred!
See also "/Users/user/dev/sandbox/nyan/build/CMakeFiles/CMakeOutput.log".
I do have flex installed. I am using a Mac. I am following the build instructions.
I don't see a reference to the the FLEX_INCLUDE_DIR
variable in source repo. The only reference I see is to FLEX_INCLUDE_DIRS
in CMakeLists.txt
:
# include directory specification
# depending if nyan was built or installed,
# projects using libnyan will automatically include the correct dir.
target_include_directories(nyan
PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/../>
$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}/>
PRIVATE
${FLEX_INCLUDE_DIRS}
)
This is a new type that matches to objects that are child of the given type.
This is useful for e.g. describing this case:
DiplomaticAbility(Ability):
stances : set(children(DiplomaticStance))
The DiplomaticStance
is a non-abstract API object, and therefore is valid to be used on its own as a value for stance. As we only want to allow children, the children(DiplomaticStance)
matches to that.
This implements preventing "too abstract" objects from being used.
Symmetric difference, disjunctive union, XOR - or whatever you call it - is a set operation where the resulting set is composed of elements which are present in either set, but not in both. The corresponding python operator is ^=
.
This operation would lower the required patches for "replacement" changes such as the replacement of a move ability.
Without XOR (2 patches):
PatchRemove<SomeUnit>(..):
abilities -= {OldMove}
PatchAdd<SomeUnit>(..):
abilities += {NewMove}
With XOR (1 patch):
PatchXOR<SomeUnit>(..)
# OldMove will be removed because it is in both sets
abilities ^= {OldMove, NewMove}
This kind of a design flaw, which we have to resolve.
The problem is that currently it is impossible that values in set
s can be added and removed at the same time. This is required when a specialization (called Special
) of a value is to be added to the set
, but the parent of Special
is already in the set
.
This is a problem because often entities define a preset of values, which then need to be specialized, and the old ones invalidated.
Possible idea:
set
insertion in such a way that some keyword (e.g. {@objectname, ...}
) marks it as unique, and child objects of this will replace it. We would need 3 operators:
@insertion
: Any child object of this insertion will delete it+insertion
: This will delete all of its parents in the set (even if those were not annotated with @
)!insertion
: This will ignore that the insertion of a child should delete this entry (-> ignore the +
annotation effect)Example:
# each unit can move by default
Unit(Entity):
Move(Ability):
speed = 1.0
abilities += {Move}
# but the villager now changes the speed!
Villager(Unit):
VillagerMove(Unit.Move):
speed = 10.0
# Move has to be replaced, otherwise we have 2x Move!
abilities += {VillagerMove}
Solution with annotations:
Unit(Entity):
Move(Ability):
speed = 1.0
abilities += {@Move}
Villager(Unit):
VillagerMove(Unit.Move):
speed = 10.0
# this now automatically replaces `Move` with `VillagerMove`
abilities += {VillagerMove}
Possible solution without annotations:
Unit:
...
Movable:
movespeed = ...
MovableUnit(Unit, Movable):
....
Villager(MovableUnit):
movespeed = 10
But this solution makes use of the inheritance, and the member names would need redundant prefixes in order to remain unique. This can be improved a bit by the member name qualifications.
Quoting @mic-e:
also irgend ein keyword oder ne andere magic damit nyan hinzugefügte nyanobjekte die unterobjekte vom nyanobjekt sind dem des member gehört automatisch upgraded bei vererbten nyanobjekten wäre halt die ideale lösung denk ich
The latest code on master compiles fine. Is it possible to publish a newer tag, or move the v0.1 tag to a "good" commit?
nyan uses advanced C++ features (c++14 relaxed constexpr) which are only available in recent compiler releases (gcc>=5 && clang>=3.4).
I successfully built nyan with clang++-3.8 but failed with g++5.3.0 because some virtual functions in nyan_object.h
are not defined. clang doesn't bother but g++ can't link libnyan.so
and is complaining about missing vtables.
Bellow the Dockerfile used to investigate this issue :
from ubuntu:14.04
RUN apt-get install -y software-properties-common wget && \
add-apt-repository ppa:ubuntu-toolchain-r/test && \
echo deb http://llvm.org/apt/trusty/ llvm-toolchain-trusty-3.8 main >> /etc/apt/sources.list && \
wget -O - http://llvm.org/apt/llvm-snapshot.gpg.key|sudo apt-key add - && \
apt-get update && \
apt-get install -y \
git gcc-5 g++-5 make libtool autoconf automake flex bison clang-3.8 clang++-3.8
WORKDIR /
RUN git clone https://github.com/SFTtech/nyan.git nyan-gcc && cp -r /nyan-gcc /nyan-clang
# GCC Build
# Build doesn't work with GCC yet
# WORKDIR /nyan-gcc
# RUN ./autogen.sh && libtoolize && aclocal && autoheader && autoconf && automake && ./configure CC=gcc-5 CXX=g++-5 && make
# Clang Build
WORKDIR /nyan-clang
RUN ./autogen.sh && libtoolize && aclocal && autoheader && autoconf && automake && ./configure CC=clang-3.8 CXX=clang++-3.8 && make
# FIXME: We shouldn't have to call every single autocancer tool. The following command line should be just enough to build nyan.
# RUN ./autogen.sh && ./configure CC=clang-3.8 CXX=clang++-3.8 && make
Additionally it seems there is also a minor issue with the autogen.sh
script.
./autogen.sh && ./configure
should do the job but I wasn't able to build nyan without calling every single autotool script :
./autogen.sh && libtoolize && aclocal && autoheader && autoconf && automake && ./configure
I'm not an expert on that matter but should we add libtoolize && aclocal && autoheader && autoconf && automake
to./autogen.sh` ? What is the established practice here ?
In openage, we need a notification event for watched nyan values.
This means nyan
needs support for registering callback functions that will fire when the value of the member changes.
We need this to avoid applying the same patches in every view.
This is currently cumbersome:
View
)Currently, each View
starts at the root state, so for each team, we would need to apply the patches the mods use for registration for each View
of the teams.
The solution is that it is possible to "fork" a view and continue from this state with a new view.
It should be possible to hot-reload nyan files when they change.
Changes in the file could either be applied by an auto-generated patch, or the whole state is replayed with the new values.
Or some other creative idea :)
For openage, a way to create arbitrary branches of views would be useful. Arbitrary branching in this context means that the view is not determined from a predefined characteristic (e.g. player civilization), but from an specific event or change during a game. The event can be defined in the API or by a script.
Branching a view can be used to create a fork of a game entity. Both the main view and the branch view will continue to exist, but both can be patched separately.
Example
A scenario script gives 5 specific swordsmen game entities a +2 attack upgrade. This upgrade triggers a fork which leads to a new branch view to be created for these 5 swordsmen. The branch view then receives the upgrades by patching the branch view, but not the main swordsman view. Thus, only the 5 swordsman benefit from the upgrade. Later on, a blacksmith upgrade gives all swordsman +1 attack. Internally, both the branch view and the main view are patched. The 5 specific swordsmen still have 2 more attack than all other swordsmen.
The patch target type is inherited, and the defining object of the patch target and all its children are a patch.
This needs to be clarified in the specification.
Currently, the int
data type operations only allow integers as operands. This limits operations on int
members somewhat, especially for multiplications.
int
operations other than =
should also allow floats to be used as operands. The result of the operation should be typecast internally to int
, i.e. the resulting value is floored.
SomeObject():
member : int = 4
other_member : int = 9
Patch<SomeObject>():
member *= 2.5f # result: member = 10 (multiplication: 4 * 2.5 = 10.0 -> 10)
other_member *= 1.5f # result: c = 13 (multiplication: 9 * 1.5 = 13.5 -> 13)
Currently, all member access is checked at load time. When patches add new members to objects by addint inheritance parents, those cannot be used (because at load time the member was not there).
It may be good to allow access to newly added member within the patch that introduces them through adding the parents.
But: what about double-patching (patching this patch again)? Will the used member be overwritten? We should not initialize the new member again!
Example where a newly added member (Provider.resources
) is changed within the patch that added it (Provider
).
# engine:
Resource():
...
ResourceSpot():
Amount():
type : Resource
amount : float
resources : set(Amount)
# game:
DeadVillager(Villager):
...
Meat(Resource):
...
# mod:
# cannibalism mod that adds dead villager becoming a resource spot
VillagerMeat(ResourceSpot.Amount):
type = Meat
amount = 120.0
Provider<DeadVillager>[+ResourceSpot]:
# here, we patch the dead villager, that did not have the resources member before!
resources = {VillagerMeat}
We should allow the patch target to be defined for abstract patch members.
Example syntax:
TransformTo():
enable_abilities : Patch<Transform>
Performance optimization: Don't calculate the aggregated value every time, instead cache it.
The complicated part is that a parent node, which influences the cached value, has to invalidate the cache.
We could do this by checking all parents of the cached value if they changed, but that could be more overhead than just calculating the value every time.
We can likely decide between patch applications or value retrieval to be efficient.
The patch application could invalidate/recalculate all caches of values that it changes, which takes some time. Or we can directly apply the patch, but the value get would need to check for cache validity then.
As this is an optimization, some investigation and profiling would be useful first.
Some ideas:
To allow collections of any patch, there needs to be a common type. This can be created manually, but this parent object should be built-in.
-> Add a Patch
object that is defined as built-in.
Actually this is very easy, but the problem is the error reporting system, that does not support "built-in" objects (because they're not from some existing file).
The AX_CXX_COMPILE_STDCXX_14
used in ./configure.ac
might or should be provided by the autoconf-archive
system package in the file ax_cxx_compile_stdcxx_14.m4
installed system wide.
There is actually three probem with this :
autoconf-archive
is not in the dependency list in doc/building.md
ax_cxx_compile_stdcxx_14.m4
depends on ax_cxx_compile_stdcxx.m4
which is also missing from Debian based autoconf-archive
package.Workaround : install manually the files ax_cxx_compile_stdcxx_14.m4 and ax_cxx_compile_stdcxx.m4 in the ./m4
folder.
It should be possible to undo patches in a transaction (e.g. when a research is cancelled).
Two strategies are possible to update the state at that point in time:
This should be an API function, though. We have to decide on a strategy and implement it.
A suggestion for testing (what I am using in jnyan):
tests
├── 001
│ ├── main.nyan
│ └── sim
│ ├── 001.txt
│ └── 002.txt
└── 002
├── engine.nyan
├── main.nyan
└── sim
├── 001.txt
└── 002.txt
Where the root folders are test cases with loaded file being the main.nyan (which may include import statements) and in the sim (= simulation) folders there are files with commands in order to simulate a scenario. Every line is command, mainly assert like commands but also execution of patches.
// optionally set a namespace
namespace main
// asserts
Barracks creates == [main.Jedi, main.Clone]
Barracks hp == 4000
Barracks model == ./models/m1.dat
// assert number of members
Unit # 3
Infantry # 3
Jedi hp == 300
Master hp == 480
// apply a patch
patch ArmorUpgrade
Jedi hp == 310
Master hp == 490
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.