Code Monkey home page Code Monkey logo

hsm's Introduction

Hana State Machine (HSM)

Linux CI MacOs CI Windows CI

codecov GitHub license GitHub contributors GitHub release Donate Join the chat at https://gitter.im/hsm-gitter/community

The hana state machine (hsm) is a finite state machine library based on the boost hana meta programming library. It follows the principles of the boost msm and boost sml libraries, but tries to reduce own complex meta programming code to a minimum.

The following table compares features among popular c++ state machine libraries. A click on a particular feature check mark will forward to the feature documentation.

Feature Hsm Sml Msm Statechart
External transition
Anonymous transition (Completion)
Internal transition
Direct transition
Guards / actions
Entry / exit actions
Orthogonal regions
Hierachies / sub state machines
Event defering
Transition logging ?
Initial pseudo state
History pseudo state
eUml postfix frontend
eUml prefix frontend
Entry / exit pseudo state
State data members
Unexpected event / no transition handler
Dependency injection
Single amalgamation header
Custom target state construction
Chain actions ?

Example (Run)

#include "hsm/hsm.h"

#include <iostream>
#include <cassert>

// States
struct Locked {
};
struct Unlocked {
};

// Events
struct Push {
};
struct Coin {
};

// Guards
const auto noError = [](auto /*event*/, auto /*source*/, auto /*target*/) { return true; };

// Actions
constexpr auto beep
    = [](auto /*event*/, auto /*source*/, auto /*target*/) { std::cout << "beep!" << std::endl; };
constexpr auto blink = [](auto /*event*/, auto /*source*/, auto /*target*/) {
    std::cout << "blink, blink, blink!" << std::endl;
};

struct Turnstile {
    static constexpr auto make_transition_table()
    {
        // clang-format off
        return hsm::transition_table(
            // Source              + Event            [Guard]   / Action = Target
            // +-------------------+-----------------+---------+--------+----------------------+
            * hsm::state<Locked>   + hsm::event<Push>           / beep   = hsm::state<Locked>  ,
              hsm::state<Locked>   + hsm::event<Coin> [noError] / blink  = hsm::state<Unlocked>,
            // +--------------------+---------------------+---------+--------+------------------------+
              hsm::state<Unlocked> + hsm::event<Push> [noError]          = hsm::state<Locked>  ,
              hsm::state<Unlocked> + hsm::event<Coin>           / blink  = hsm::state<Unlocked>
            // +--------------------+---------------------+---------+--------+------------------------+                        
            );
        // clang-format on
    }
};

auto main() -> int
{
    hsm::sm<Turnstile> turnstileSm;

    // The turnstile is initially locked
    assert(turnstileSm.is(hsm::state<Locked>));

    // Inserting a coin unlocks it
    turnstileSm.process_event(Coin {});
    assert(turnstileSm.is(hsm::state<Unlocked>));

    // Entering the turnstile will lock it again
    turnstileSm.process_event(Push {});
    assert(turnstileSm.is(hsm::state<Locked>));

    return 0;
}

Play with it Online

Runtime Benchmark Results

The benchmark result are taken from the state machine benchmark repository.

Benchmark Hsm Sml Msm Statechart
Simple state machine 99 ms 17 ms 18 ms 443 ms
Complex state machine 818 ms 978 ms 881 ms 1374 ms

Compiletime Benchmark Results

Benchmark Hsm Sml Msm Statechart
Simple state machine 6.41 s 0.62 s 5.17 s 1.52 s
Complex state machine 41.99 s 3.01 s 25.54 s 4.27 s

Compilememory Benchmark Results

Benchmark Hsm Sml Msm Statechart
Simple state machine 174.649 MB 28.474 MB 404.621 MB 70.976 MB
Complex state machine 815.720 MB 188.333 MB 1323.477 MB 122.720 MB

Dependencies

  • Boost 1.72
  • C++17
  • >= g++-8
  • >= clang-8
  • Cmake 3.14

Dev Dependencies

  • Gtest

Integration

Usage as Single Header

  • Download amalgamation header and put it into your project src folder
  • Include amalgamation header:
    #include "path/to/amalgamation/header/hsm.h"

CMake

To use this library from a CMake project, you can locate it directly with find_package() and use the namespaced imported target from the generated package configuration:

# CMakeLists.txt
find_package(hsm 1.3.5 REQUIRED)
...
add_library(foo ...)
...
target_link_libraries(foo PRIVATE hsm::hsm)

Since CMake v3.11, FetchContent can be used to automatically download the repository as a dependency at configure time. You can follow this example and implement the following snippet:

include(FetchContent)

FetchContent_Declare(hsm
  GIT_REPOSITORY https://github.com/erikzenker/hsm.git
  GIT_TAG v1.4.7)

FetchContent_GetProperties(hsm)
if(NOT hsm_POPULATED)
  FetchContent_Populate(hsm)
  add_subdirectory(${hsm_SOURCE_DIR} ${hsm_BINARY_DIR} EXCLUDE_FROM_ALL)
endif()

target_link_libraries(foo PRIVATE hsm::hsm)

If you are using CPM.cmake, you can follow this example. Implement the following snippet:

include(cmake/CPM.cmake)

CPMAddPackage(
    NAME hsm
    GITHUB_REPOSITORY erikzenker/hsm
    VERSION 1.4.7)

target_link_libraries(foo PRIVATE hsm::hsm)

Package Managers

If you are using Conan to manage your dependencies, merely add hsm/x.y.z to your conanfile's requires, where x.y.z is the release version you want to use. Please file issues here if you experience problems with the packages.

Install

CMake

cmake -S . -B build
cmake --install build/ --prefix /tmp/

Conan/Cmake

mkdir -p build/dependencies/conan
conan install . -if build/dependencies/conan -s compiler.libcxx=libstdc++11 --build missing
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release -D "CMAKE_PREFIX_PATH=${PWD}/build/dependencies/conan"
cmake --install build/ --prefix /tmp/

Conan Download

conan remote add conan-erikzenker https://api.bintray.com/conan/erikzenker/conan-erikzenker
conan install hsm/1.0@erikzenker/testing --build missing

Install from Arch Linux AUR

pacaur -S hsm-git

Compile and Run the Tests Using the Installed Library

cmake -S test -B build
cmake --build build/test
cd build/test
ctest --output-on-failure

Compile and Run the Tests Using Conan

mkdir -p build/dependencies/conan
conan install . -if build/dependencies/conan -s compiler.libcxx=libstdc++11 --build missing
cmake -S test/ -B build/test -DCMAKE_BUILD_TYPE=Release -D "CMAKE_PREFIX_PATH=${PWD}/build/dependencies/conan"
cmake --build build/test/
cd build/test
ctest --output-on-failure

Author

  • erikzenker(at)hotmail.com

hsm's People

Contributors

benjaminw3 avatar erikzenker avatar erikzenkerlogmein avatar friendlyanon avatar gjasny avatar jupp0r avatar michael-baumann avatar michaelsgoto 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

hsm's Issues

[BUG] MSVC dispatch table injection fails to compile

Describe the bug
MSVC fails to compile if constexpr branches even if the condition for the branch does not evaluate to true.

This is needed to sm.h:

  if constexpr (has_anonymous_transition(rootState)) {
  ...
  auto& result = get_dispatch_table_entry(event, region);
  ...
  }

To Reproduce
Compiles on clang: https://godbolt.org/z/e3KcYv
Does not compile on msvc: https://godbolt.org/z/ab3bdPj91

Expected behavior
Compiles.

Additional context
A workaround could be to add some kind of a dummy next state for the none event.

Compose actions

Problem:
The hsm does not provide a function to compose multiple actions into a single one.

Solution:
Provide a function and operator for the dsl e.g.:

hsm::state<S1> {}          + hsm::event<e1> {} / (log, update) = hsm::state<S2> {}, // log and update get composed
hsm::state<S1> {}          + hsm::event<e1> {} / compose(log, update) = hsm::state<S2> {}, // log and update get composed

[BUG] on_exit never call for initial state

Hi,
Describe the bug
The on_exit is never called for the initial state neither in the main state nor a substate. But the on_entry is sometimes call.

To Reproduce

    #include <hsm/hsm.h>
    
    #include <iostream>
    
    #define ON_ENTRY(STATE)                                                                                                                   \
        static constexpr auto on_entry()                                                                                                      \
        {                                                                                                                                     \
            return [](const auto& event, const auto& source, const auto& target) { std::cout << "      -- ENTRY: " << #STATE << std::endl; }; \
        }
    
    #define ON_EXIT(STATE)                                                                                                                   \
        static constexpr auto on_exit()                                                                                                      \
        {                                                                                                                                    \
            return [](const auto& event, const auto& source, const auto& target) { std::cout << "      -- EXIT: " << #STATE << std::endl; }; \
        }
    
    #define ON(STATE)   \
        ON_ENTRY(STATE) \
        ON_EXIT(STATE)
    
    namespace ee {
    struct Idle {
        ON(Idle);
    };
    
    struct A0 {
        ON(A0);
    };
    // --------------------------------------------------------------------------
    // States
    struct A1 {
        ON(A1);
    };
    
    struct A2 {
        ON(A2);
    };
    
    // --------------------------------------------------------------------------
    // Events
    struct press {};
    struct start {};
    struct stop {};
    
    // --------------------------------------------------------------------------
    // Guard
    const auto success = [](auto /*event*/, auto /*source*/, auto /*target*/) { return true; };
    
    // --------------------------------------------------------------------------
    // Actions
    const auto log = [](auto event, auto source, auto target, const char* msg = "") {
        std::cout << msg << typeid(source).name() << " + " << typeid(event).name() << " = " << typeid(target).name() << std::endl;
    };
    
    // --------------------------------------------------------------------------
    // State machines
    struct SubState {
        static constexpr auto make_transition_table()
        {
            // clang-format off
                return hsm::transition_table(
                    // Source              + Event            [Guard]   / Action  = Target
                    // +-------------------+------------------+---------+---------+----------------------+
                    * hsm::state<A0>       + hsm::event<press>          / log     = hsm::state<A1>,
                      hsm::state<A1>       + hsm::event<press>          / log     = hsm::state<A2>,
                      hsm::state<A2>       + hsm::event<press>          / log     = hsm::state<A0>
                    );
    
            // clang-format on
        }
    
        ON(SubState);
    
        static constexpr auto on_unexpected_event()
        {
            return [](auto& event, const auto& state) { log(event, state, state, "unexpected event: "); };
        }
    };
    
    struct Initial {
    
        static constexpr auto make_transition_table()
        {
            // clang-format off
                return hsm::transition_table(
                    // Source              + Event            [Guard]   / Action  = Target
                    // +-------------------+------------------+---------+---------+----------------------+
                    * hsm::state<Idle>       + hsm::event<start>          / log     = hsm::state<SubState>,
                      hsm::state<SubState>   + hsm::event<stop>           / log     = hsm::state<Idle>
                    );
    
            // clang-format on
        }
    
        ON(Initial);
    
        static constexpr auto on_unexpected_event()
        {
            return [](auto& event, const auto& state) { log(event, state, state, "unexpected event: "); };
        }
    };
    
    }   // namespace ee
    
    int main()
    {
        std::cout << "------------------------------------------- Initial" << std::endl;
        {
            hsm::sm<ee::Initial> fsm;
    
            fsm.process_event(ee::start{});
    
            for (int i = 0; i < 4; ++i) {
                std::cout << "----- process_event(press) ----- " << std::endl;
                fsm.process_event(ee::press{});
            }
    
            fsm.process_event(ee::stop{});
        }
    
        std::cout << "------------------------------------------- SubState" << std::endl;
        {
            hsm::sm<ee::SubState> fsm;
    
            for (int i = 0; i < 4; ++i) {
                std::cout << "----- process_event(press) ----- " << std::endl;
                fsm.process_event(ee::press{});
            }
        }
    }

Expected behavior
On_exit called every time a state is exited (at least when the on_entry was called)

Additional context

output:

------------------------------------------- Initial (with a substate)
struct ee::Idle + struct ee::start = struct ee::A0
-- ENTRY: SubState
-- ENTRY: A0
----- process_event(press) -----
here expecting EXIT: A0

struct ee::A0 + struct ee::press = struct ee::A1
-- ENTRY: A1
----- process_event(press) -----
-- EXIT: A1
struct ee::A1 + struct ee::press = struct ee::A2
-- ENTRY: A2
----- process_event(press) -----
-- EXIT: A2
struct ee::A2 + struct ee::press = struct ee::A0
-- ENTRY: A0
----- process_event(press) -----

here expecting EXIT: A0

struct ee::A0 + struct ee::press = struct ee::A1
-- ENTRY: A1
-- EXIT: SubState
<- here the sate on_exit is called which is great
struct ee::SubState + struct ee::stop = struct ee::Idle
-- ENTRY: Idle
<- here ENTRY is called but was not called at the start, nor the EXIT is called

------------------------------------------- SubState
----- process_event(press) -----
here, may be EXIT expected, may by ENTRY too for A0
struct ee::A0 + struct ee::press = struct ee::A1
-- ENTRY: A1
----- process_event(press) -----
-- EXIT: A1
struct ee::A1 + struct ee::press = struct ee::A2
-- ENTRY: A2
----- process_event(press) -----
-- EXIT: A2
struct ee::A2 + struct ee::press = struct ee::A0
-- ENTRY: A0
----- process_event(press) -----
here ENTRY: A0 was called, but not EXIT
struct ee::A0 + struct ee::press = struct ee::A1
-- ENTRY: A1

Unfortunately, the code itself is beeyond my template/boost.Hana knowledge, If I have some tips to find the issue or change the behavior, may be I can help.

Regards,

Post Fix Transition Dsl

Current list notation:
hsm::transition(src_state, event, guard, action, dst_state)
In pre fix notation:
dst_state <= src_state + event [ guard ] / action

Substate entry action is only called when transition has a action

Problem:
a transition into a substate like the following does not call the substates entry action

  * hsm::state<S1>{} + hsm::event<enterSubState>{}   = hsm::state<SubState>{}

Workaround:
it does work when you provide a dummy action:

  * hsm::state<S1>{} + hsm::event<enterSubState>{}   / dummy = hsm::state<SubState>{}

Make dependency reassignable

...so the following use case can be supported (https://cppx.godbolt.org/z/YdxpJ6):

#include <memory>
#include <string>
#include <iostream>

struct MyDependency {
    std::string data = "42";
};

template <class Dependency>
struct StateMachine {
    StateMachine(Dependency& dependency) : dependency(dependency)
    {        
    }

    Dependency& dependency;
};


struct A {
    A() : 
      dep(MyDependency{}), 
      sm(StateMachine<MyDependency>{dep}){
        
    }

    A(A&& other) : 
        dep(std::move(other.dep)),
        sm(std::move(other.sm))
    {
        sm.dependency = dep;
    }

    MyDependency dep;
    StateMachine<MyDependency> sm;

};

int main() {
    A a;
    
    std::cout << "before move:" << std::endl;
    std::cout << "dep: " << a.dep.data << std::endl;
    std::cout << "ref: " << a.sm.dependency.data << std::endl;

    auto b = std::move(a);

    std::cout << "after move:" << std::endl;
    std::cout << "dep: " << b.dep.data << std::endl; 
    std::cout << "ref: " << b.sm.dependency.data << std::endl;

}

[FEATURE] Improve error on unknown event type is processed

Problem

In the case you call hsm.process_event(MyEvent{}) with an event type that is not known to the state machine than a complex error is printed by the compiler.

Proposed Solution

Assert statically that the event processed is part of the list of events.

[FEATURE] Documentation. More fsm comparison criteria

I have a complex state machine (with subs) with several dozen states and transitions. Released on boost::msm.

And I have 2 problems with it:

  1. Long compilation time. About 2 minutes for any change of code.
  2. The huge amount of memory required by the compiler to compile the given state machine. I saw with my own eyes the consumption of 7 GB of memory at its peak.

I can forgive the lengthy compilation. There are mechanisms that have helped me reduce the need to constantly recompile the state machine.

But the huge amount of memory for the compiler confuses me a lot. This required me to use VS 2022 (in Preview state) and compile a 64-bit application.

I'm in the middle of looking for a finite state machine library with similar functionality to boost::msm, but requiring less memory to compile (less than 4GB).

Please add an approximate comparison of the memory consumed by the compiler when compiling equivalent state machines for the boost::msm and hsm implementations.

[FEATURE] Access to Parent Source State on entry of SubState

Problem
The on_entry action of a state does not get the parent source state as a parameter. But the parent source state could be handy when you need to access its data members e.g. to start a timeout.

Current Workaround
Instead of storing data in the parent source state you can add additional members to the dependency struct.

Proposed Solution

Provide the parent source state to the on_entry function. The same holds true for the parent target state for the on_exit function.

One Dimensional DispatchTable

The current dispatch table is two dimensional with the indexes current parent state and parents state e.g.: (nextParentState, nextState) = dispatchTable[currentParentState][currentState]

The two level index access could be replaced with a single index access. This would improve performance.

[FEATURE] SubSub...State explicit exits from RootState

Problem

If you are currently in a sub sub .... sub state of your state machine and an event is triggered triggered in the root state machine then this event is unexpected.

Current Workaround

Don't build state machines with substate level higher then one.

Proposed Solution

When generating substate exits also consider more levels.

[FEATURE] Call a state activity

Problem

How to run a state activity when I'm on a given state ?

An action is instantaneous: it does not consume time (contrary to the activity). The activity can be seen as a thread that is preempted by any external events the state reacts to. https://cs.emis.de/LNI/Proceedings/Proceedings07/TowardEfficCode_3.pdf

struct MyState {
  MyState(hsm::sm<MyFSM>& fsm)
    : m_sfm(fsm)
   {}

  void tickActivity()
  {
      // do_something not blocking too much
      // Allow m_fsm.process_event(EventXXX {});
  }

  sm::sm<MyFSM>& m_fsm;
};

main()
{
  hsm::sm<MyFSM> fsm;
  while (true)
  {
      fsm.tickActivity(); // Call the current state tickActivity()
      pause(x_ms);
  }
}

Support state data members

The boost msm supports state data members. Which means that your states can have members which you can access on events and actions.

E.g.:

    struct action {
        template <class FSM, class EVT, class SourceState, class TargetState>
        void operator()(EVT const& e, FSM& fsm, SourceState& source, TargetState& target)
        {
           if(target.hasMember){
             std::cout << "this state has a member" << std::endl;
           }
        }
    };

Support Generalized Lamdas as Actions

Actions need to fit onto a particular event e.g.:

const auto action = [](e6 event) {event.called->set_value();};

But it is common that actions are used by different events. In this case, a generalized lambda would be useful:

const auto action = [](auto event) {event.called->set_value();};

The generalized lambda does currently not compile because of the "weird" way actions are called in the library.

Support states which are non default constructable

Problem:
States need to be default constructable. Thus you can't use a constructor to initialize state data members.

Current workaround:
Initialize a state in its entry action by setting its members or calling an init function

struct Target {
    constexpr auto on_entry()
    {
        return [](auto event, auto source, auto& target) { 
          target.member = std::string("init String"); 
       };
    }
};

Proposed solution by @BenjaminW3:
provide a way to use the constructor of a class and every action call would create a new instance of the target class such as:

const auto action = [](auto&& event, auto source) {
  return Target{std::string("Init string")};
};

or return an instance which is saved externally:

const auto action = [](auto&& event, auto source, auto& dependency) {
  return dependency.target;
}

[FEATURE] Provide state information to unexpected event handler

Problem

The unexpected event handler does currently only get the event as an parameter, but the current state is unknown to the handler. Thats makes it harder to debug in which state the unexpected event was triggered.

Proposed Solution

Provide the current state and parent state to the event handler (at least a string representation of it)

[BUG] Segfault in process_events

Describe the bug
This segfault appeared when i try to use orthogonal regions with hierarchies.

To Reproduce
https://godbolt.org/z/nzT5MYG91

Expected behavior
I expect this test to pass successfully:

int main()
{
    hsm::sm<MainState> sm;

    sm.process_event(switch_{});
    BOOST_ASSERT(sm.is(0, hsm::state<Online>, hsm::state<S1>));
    BOOST_ASSERT(sm.is(1, hsm::state<Online>, hsm::state<S3>));

    sm.process_event(e1{});
    BOOST_ASSERT(sm.is(0, hsm::state<Online>, hsm::state<S2>));
    BOOST_ASSERT(sm.is(1, hsm::state<Online>, hsm::state<S4>));

    return 0;
}

Additionally, I expect the console output to be:

Region 0!
Region 1!

Additional context
This works good when i remove "Region 1":
https://godbolt.org/z/Kfbe3zean

Benchmark compile time

Record the compile time during ci benchmark compilation of the single benchmark targets.

Cleanup namespace pollution

In multiple files the the namespace is polluted by boost::hana through the alias bh

namespace bh {
using namespace boost::hana;
}

[BUG] MSVC_RUNTIME_LIBRARY in tests

Describe the bug

  • The MSVC_RUNTIME_LIBRARY property is only available since CMake 3.15, but I forgot to check this in my PR and the minimum required CMake version in the test CML is 3.14.
  • The value of this property is not among the supported set of values.

To Reproduce
-

Expected behavior

  • The minimum requirement shouldn't be a lie.
  • The value of the property (if this property is even necessary) should be valid.

Additional context
Is this property even necessary?
The VS generator for CMake already uses /MDd for Debug configuration and /MD for everything else.
Is statically linking the CRT for the tests important?

[FEATURE] Guard Based Decision Transitions

Problem
The following transition table shows two transitions which only differ on the guard:

    static constexpr auto make_transition_table()
    {
        // clang-format off
        return hsm::transition_table(
            hsm::state<NotPressed> {} + hsm::event<press> {}                  / blinkOnce  = hsm::state<Pressed> {},
            hsm::state<Pressed> {}    + hsm::event<release> {} [shortPressed] / blinkShort = hsm::state<NotPressed> {},
            hsm::state<Pressed> {}    + hsm::event<release> {} [longPressed]  / blinkLong  = hsm::state<NotPressed> {}
        );
        // clang-format on
    }

Currently, the second transition overwrites the first one.

Current Workaround
You can put some logic in front of the state machine which triggers two different events e.g.: releaseAfterShortPressed and releaseAfterLongPressed

Proposed Solution
One event points to a list of next states in the dispatch table. In the case only a single next state is present performance should not be degraded.

Don't pay for what you don't use

The current run time overhead of hsm compared to sml or msm is quite a lot since both sml and msm are deactivating every feature you don't use at compile time. Hsm should also deactivate feature when they are not used. The following list gives an idea of the overhead:

  • Call (dummy) action even if no actions are specified ~20% overhead

  • Call (dummy) guard even if no guards are specified ~16% overhead

  • Check for anonymous transitions even if there are not specified ~16% overhead

  • Loop over regions even if there is only one region ~14% overhead

  • Check for defers even if no events are deferred ~6% overhead

  • Save history even if no history pseudo states are used ~3% overhead

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.