Code Monkey home page Code Monkey logo

nyan's Introduction

nyan - yet another notation

just make it meta-meta-meta.

nyan is a data description language, It is a mixture of python, json, patch, wml, yaml and some new ideas.

It stores hierarchical objects with key-value pairs in a database with the key idea that changes in a parent affect all children.

We created nyan because there existed no suitable language to properly represent the enormous complexity of storing the data for openage.

The main focus is readability and moddability.

github stars #sfttech on matrix.org

The foundation of nyan:

Technology Component
C++20 nyan core
Flex Tokenizer generator
CMake Build "system"
Humans Doing it wrong all the time

How?

Let's assume we have a fun λ half-life strategy game.

The game engine provides some functionality which is exposed by nyan.

# This is the content and mod API of the Engine:

Unit():
    hp : int
    animation : file

Building():
    hp : int
    creates : set(Unit)
    model : file

Using this, the base game pack provides actual game content:

# base_game_data.nyan

OverwatchSoldier(Unit):
    hp = 50
    animation = "./assets/soldier.ani"

Strider(Unit):
    hp = 2100
    animation = "./assets/strider.ani"

CombineCitadel(Building):
    hp = 9001
    creates = {OverwatchSoldier, Strider}
    model = "./assets/lambda_hq.mdl"

Citizen(Unit):
    hp = 60
    animation = "./assets/male09.ani"

# gordon is a citizen with more hp
Gordon(Citizen):
    hp += 40
    animation = "./assets/gordon.ani"

RebelHQ(Building):
    hp = 5000
    creates = {Citizen, Gordon}
    model = "./assets/lambda_hq.mdl"

Now, let's create a mod that adds the overwatch elite and gives the striders even more hp.

# elite_strider_mod.nyan

# create a new unit:
OverwatchElite(Unit):
    hp = 70
    animation = "./assets/coolersoldier.ani"

# change the strider:
ChangeStrider<Strider>():
    hp += 1000

# change the citadel to build the elite:
AddElite<CombineCitadel>():
    creates += {OverwatchElite}

# create a mod that informs the engine about its patches
StriderEliteMod(Mod):
    name = "Add the elite and make striders stronger"
    patches = {AddElite, ChangeStrider}

When the engine activates the mod ("applies the patches"), the combine citadel can create the new unit and the strider is stronger.

The fun begins if you now create a mod that mods the mod. Which is totally possible with nyan.

Specification

Read the specification.

Current State of the Project

nyan is fully functional and can be used in your project. Please make us aware of your needs/experiences in our chat!

We try to keep the API stable, but there's still some unknowns and need-to-change features.

Please submit bugs and feature requests (and patches) on GitHub!

Dependencies, Building and Running

Operating System Build status
Debian Sid Todo: Kevin #11
  • How do I get this to install on my box?

  • Waaaaaah! It

    • crashes
    • spams all kinds of shit on the screen
    • my girlfriend dumped me because I debugged for nights

All of those observations are intended, not bugs.

To get rid of them, recompile with --dont-segfault --shut-up --new-girlfriend.

If this still does not help, try the contact section or the bug tracker.

Development Process

How does contributing work here?

The documentation is also in this repo:

  • Code documentation is embedded in the sources for Doxygen (see doc readme).
  • Have a look at the doc directory. This folder tends to outdate when code changes.

Contact

If you have the desire to perform semi-human interaction, join our Matrix chatroom!

For ideas, problems, ..., use the issue tracker!

If it's a problem with the usage of nyan in openage, head over there.

License

GNU LGPLv3 or later; see copying.md and legal/LGPLv3.

We know that probably nobody is ever gonna look at the copying.md file, but if you want to contribute code to nyan, please take the time to skim through it and add yourself to the authors list.

nyan's People

Contributors

ackxolotl avatar apainintheneck avatar ax3l avatar coco875 avatar eaterjolly avatar heinezen avatar inakoll avatar kawzeg avatar maanooak avatar martinfx avatar mrwerdo avatar simonsan avatar thejj avatar trodge avatar tusharpm avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

nyan's Issues

Implement member name qualifications

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.

The set inheritance modification problem

This kind of a design flaw, which we have to resolve.

The problem is that currently it is impossible that values in sets 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

Mitigate runtime errors for inf values

We have thought about amending the nyan language spec to mitigate runtime errors:

  • If an optional(..) type member has the None value assigned, all relative operations (+=, *=, etc.) on the value will have the result None.
  • The inf value for the int/float types can only be assigned -> Relative operation using inf as the operand will be disallowed.
  • The special case inf *= 0 will cause a RuntimeError

From: https://matrix.to/#/!iPmyRMGnDgSMPGqgaX:matrix.org/$w2yaN1vNl21LMYIU-CMcu2nNJDGHoLr1l1OhJP-BtwI?via=matrix.org&via=sft.lol&via=stusta.de

Python API

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 -> common notation format converter

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

Qualification aliases

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

Improve lookup time for keyframe for requested time

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).

Hot reloading

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 :)

orderedset.h build error

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



Allow patch to inherit from a patch that targets the same object

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.

Number types compatibility (int<->float) considerations

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

children(type) to allow children only

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.

Value caching

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:

Is this still active?

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.

Let objects be declared as abstract even if they contain no members

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)

lsp server support

nyan could provide a lsp server for completion and formatting in editors that support lsp.

Revert patches in a already-committed transaction

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:

  • Generate an inverse patch and apply it to that state
  • Get all patches that led from the previous to this state and remove the canceled ones from that list. Delete this state and go back to the previous one. Create a new state with a transaction that contains all those patches (except the cancelled ones).

This should be an API function, though. We have to decide on a strategy and implement it.

Store file names relatively to nyan-file for file member type

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/.

Empty new_parents list is created but should just be omitted

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
          }
        }
      }
    }
  }
}

Dict and dict operations

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

Building nyan with GCC

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 ?

Make failed on macOS: Undefined symbols for architecture x86_64

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

Clarify how Patch targets are inherited

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.

Change triggers for objects/members

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.

How do I add a building in a mod?

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?

Tests

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

Source: jnyan/src/test/resources/tests/000

Wholesome CI (Win/Mac)

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.

  • Grant Organisation Access to Appveyor
  • Activate nyan repository in Travis options
  • Register organization account with Appveyor to integrate with Github checks
  • grant GitHub-Team access
  • Move CI from personal account to this organisation account
  • Move from Travis to Github Workflow for Win and MacOSX

Update: With Github Workflows and Actions we could get away from Travis and Appveyor also here (SFTtech/openage#1196).

Allow branching the views after transactions were applied

We need this to avoid applying the same patches in every view.

This is currently cumbersome:

  • First, apply mods
  • Then, create teams (each needs a View)
  • Then apply the team boni patches

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.

Getting a CMake Error on FLEX_INCLUDE_DIR

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}
)

optional-type

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.

Allow storing abstract object references in members

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

Thoughts about the current specification

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. :)

  • Generell nicht so überzeugt von Vererbung zur Darstellung von Daten (Diamantenabhängigkeit = Teufels Küche)
  • Warum einen Unterschied zwischen Patches und Objekten an sich machen?
  • +1 Namespaces
  • Wie patcht man etwas, wenn man die anderen Patches eventuell nicht sieht und nicht kontrollieren kann? In welcher Reihenfolge werden die Patches dann angewandt?
  • Warum sind alle Values Maybe-Typen?
  • Was heißt Strongly typed? Ein paar Primitives? Generics? Higher-Order-Types? :)
  • += bei Inheritance heißt, dass die Daten doch Logik enthalten (nur halt versteckt)
  • Wie wird die Liste an Patches aufgebaut? Welcher Patch hat Priorität? Was passiert bei Clash?
  • +1 Gut auf die Anforderungen des Spiels zugeschnitten
  • Unit hierarchy wird sehr schnell an die Grenzen stoßen. Was passiert, wenn Archer zwar eine Ranged Unit ist, aber noch einen Bonus b bekommt, wenn er von der Rasse Mensch ist? Für eine solche Geschichte würde ich ein Entity Component System anwenden. https://en.wikipedia.org/wiki/Entity_component_system
    http://gamedevs.org/uploads/data-driven-game-object-system.pdf
  • Bei den Beispielen bin ich mir nicht mehr so sicher, ob es nicht doch bereits ein Entity Component System ist oder zumindest abbilden kann.
  • 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.
    Nach einiger Überlegung definiert ihr im Endeffekt hier Typklassen (die Vererbung beinhalten), aber auch Datentypen mit Vererbung. Das könnte zu lustigen Effekten führen, Haskell hat sich z.B. absichtlich dafür entschieden, dass Datentypen keine Vererbung beinhalten, da man sonst nur noch sehr wenig kontrollieren kann / das Typchecking echt lustig wird.
    Eine weitere Überlegung, die man machen sollte, sind sogenannte “orphan instances” (Typklassen-Lingo). Das bedeutet, dass man die Instanz eines Typen nicht in einem Modul definieren kann, das nicht die Typklasse oder den Typen definiert.
  • Wie wird dann im Endeffekt Logik gemacht? (Funktionen sind übrigens auch „nur“ Datentypen und Code is Data. :P)

English translation of my braindump (wrote it in a linear fashing while reading through the spec):

  • generally not very convinced about using inheritance to represent data (diamond-shaped subclasses = hell)
  • Why differentiate between patches and objects?
  • +1 namespaces
  • How to patch something if one doesn’t know about the other patches that will get applied? What order to apply these patches in?
  • Why are all types Maybe-types (that is: values are optional?)
  • What does strongly typed mean? Some primitives? Generics? Higher-order types? :)
  • += combined with inheritance means the files do contain some logic (but hidden)
  • How to build up a list of patches? What patch is prioritized? What happens on conflict?
  • +1 well suited to the requirements of OpenAge
  • Unit hierarchy will hit its limits pretty fast. For example, what happens when Archer is a ranged unit, but a bonus should be applied if its race is Human?
    Personally I’d try to implement an Entity Component System https://en.wikipedia.org/wiki/Entity_component_system
    http://gamedevs.org/uploads/data-driven-game-object-system.pdf
  • When I look at the examples, I’m not sure anymore whether nyan maybe does already implement some kind of CES, or at least can be used to represent one
  • 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 Teleport
    After some thinking I came to the conclusion that nyan basically defines typeclasses (which contain inheritance by default), but data types with inheritance as well. That could lead to interesting effects; Haskell has deliberately decided that data types should not have any inheritance, because it’s very hard to control stuff otherwise / typechecking gets really … interesting (cf. Scala)
    Another thing that should be considered are “orphan instances”, where an instantiation of a typeclass for a type is forbidden in a module that does not at least contain the definition of the type or the typeclass.
  • In the end, how is logic for the data implemented? (functions are also only data types btw, and code = data. :P)

Symmetric difference set operation

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}

Documentation generation

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.

Restrict namespace usage

Allow to use objects and namespaces only if they were imported properly.

Currently all objects are available, because no check for imports is performed.

Branch views

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.

Commas

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...

Allow usage of new members introduced by patch through inheritance

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}

Use nyan file version number for parsing

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():
   ...

Missing AX_CXX_COMPILE_STDCXX_14 macro

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 :

  1. The autoconf-archive is not in the dependency list in doc/building.md
  2. The file is present in the Arch Linux package but is absent in the Ubuntu (16.04) package.
  3. The file 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.

Add Patch as a built-in object

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).

Defer(?) operations

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

__init__.nyan support

Add 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.

Allow operations with float values for int data type

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)

Allow undefined set member to require a minimum number of objects

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.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.