Giter Site home page Giter Site logo

xhawk18 / s_task Goto Github PK

View Code? Open in Web Editor NEW
591.0 31.0 87.0 18.78 MB

awaitable coroutine library for C

License: Other

Assembly 7.49% Shell 0.14% C 87.90% C++ 0.01% HTML 2.46% Rich Text Format 1.19% CMake 0.13% Makefile 0.19% Python 0.29% M4 0.10% JavaScript 0.01% Batchfile 0.09%
async coroutine libuv network

s_task's Introduction

s_task - full platform multi-task library for C

中文版文档 (chinese version)

Table of content

Features

  • "s_task" is a coroutine library written in pure C and asm (from boost library), without C++ required.
  • supports various platforms, such as windows, linux, android, macos, stm32, stm8, arduino, etc.
  • supports keywords __await__ and __async__ . 🚩 For functions that may switch to other tasks, call it with 1st parameter __await__, for the caller function of which, define the 1st parameter as __async__, which make it is clear to know about context switching.
  • works with libuv for network programming.
  • "chan", "mutex" and "event" for communication between tasks.
  • on embedded platfrom (stm32/stm8/m051/arduino,etc), "s_task" is a special RTOS --
    • no dynamical memory allocation
    • very small memory footprint ( increased by ROM<1.5K, RAM<128 bytes + task stack size)

Examples

Example 1 - simple task creation

#include <stdio.h>
#include "s_task.h"

void* g_stack_main[64 * 1024];
void* g_stack0[64 * 1024];
void* g_stack1[64 * 1024];

void sub_task(__async__, void* arg) {
    int i;
    int n = (int)(size_t)arg;
    for (i = 0; i < 5; ++i) {
        printf("task %d, delay seconds = %d, i = %d\n", n, n, i);
        s_task_msleep(__await__, n * 1000);
        //s_task_yield(__await__);
    }
}

void main_task(__async__, void* arg) {
    int i;
    s_task_create(g_stack0, sizeof(g_stack0), sub_task, (void*)1);
    s_task_create(g_stack1, sizeof(g_stack1), sub_task, (void*)2);

    for (i = 0; i < 4; ++i) {
        printf("task_main arg = %p, i = %d\n", arg, i);
        s_task_yield(__await__);
    }

    s_task_join(__await__, g_stack0);
    s_task_join(__await__, g_stack1);
}

int main(int argc, char* argv) {

    s_task_init_system();
    s_task_create(g_stack_main, sizeof(g_stack_main), main_task, (void*)(size_t)argc);
    s_task_join(__await__, g_stack_main);
    printf("all task is over\n");
    return 0;
}

Example 2 - asynchronized http client without callback function.

void main_task(__async__, void *arg) {
    uv_loop_t* loop = (uv_loop_t*)arg;

    const char *HOST = "baidu.com";
    const unsigned short PORT = 80;

    //<1> resolve host
    struct addrinfo* addr = s_uv_getaddrinfo(__await__,
        loop,
        HOST,
        NULL,
        NULL);
    if (addr == NULL) {
        fprintf(stderr, "can not resolve host %s\n", HOST);
        goto out0;
    }

    if (addr->ai_addr->sa_family == AF_INET) {
        struct sockaddr_in* sin = (struct sockaddr_in*)(addr->ai_addr);
        sin->sin_port = htons(PORT);
    }
    else if (addr->ai_addr->sa_family == AF_INET6) {
        struct sockaddr_in6* sin = (struct sockaddr_in6*)(addr->ai_addr);
        sin->sin6_port = htons(PORT);
    }

    //<2> connect
    uv_tcp_t tcp_client;
    int ret = uv_tcp_init(loop, &tcp_client);
    if (ret != 0)
        goto out1;
    ret = s_uv_tcp_connect(__await__, &tcp_client, addr->ai_addr);
    if (ret != 0)
        goto out2;

    //<3> send request
    const char *request = "GET / HTTP/1.0\r\n\r\n";
    uv_stream_t* tcp_stream = (uv_stream_t*)&tcp_client;
    s_uv_write(__await__, tcp_stream, request, strlen(request));

    //<4> read response
    ssize_t nread;
    char buf[1024];
    while (true) {
        ret = s_uv_read(__await__, tcp_stream, buf, sizeof(buf), &nread);
        if (ret != 0) break;

        // output response to console
        fwrite(buf, 1, nread, stdout);
    }

    //<5> close connections
out2:;
    s_uv_close(__await__, (uv_handle_t*)&tcp_client);
out1:;
    uv_freeaddrinfo(addr);
out0:;
}

Example 3 - control LED with multitasking on ardinuo

#include <stdio.h>
#include "src/s_task/s_task.h"

//This program demonstrates three tasks:
// 1) main_task - 
//    Wait 10 seconds and set flag g_exit. 
//    After all tasks finished, set LED on always.
// 2) sub_task_fast_blinking -
//    Set led blinking fast
// 3) sub_task_set_low -
//    Set led off for 1 second, and then blinking for 3 seconds.


void setup() {
    // put your setup code here, to run once:
    pinMode(LED_BUILTIN, OUTPUT);
}


char g_stack0[384];
char g_stack1[384];
volatile bool g_is_low = false;
volatile bool g_exit = false;

void sub_task_fast_blinking(__async__, void* arg) {
    while(!g_exit) {
        if(!g_is_low)
            digitalWrite(LED_BUILTIN, HIGH); // turn the LED on

        s_task_msleep(__await__, 50);        // wait for 50 milliseconds
        digitalWrite(LED_BUILTIN, LOW);      // turn the LED off
        s_task_msleep(__await__, 50);        // wait for 50 milliseconds
    }    
}

void sub_task_set_low(__async__, void* arg) {
    while(!g_exit) {
        g_is_low = true;                     // stop fast blinking
        digitalWrite(LED_BUILTIN, LOW);      // turn the LED off
        s_task_sleep(__await__, 1);          // wait for 1 second
        g_is_low = false;                    // start fast blinking
        s_task_sleep(__await__, 3);          // wait for 3 seconds
    }    
}

void main_task(__async__, void* arg) {
    // create two sub tasks
    s_task_create(g_stack0, sizeof(g_stack0), sub_task_fast_blinking, NULL);
    s_task_create(g_stack1, sizeof(g_stack1), sub_task_set_low, NULL);

    // wait for 10 seconds
    s_task_sleep(__await__, 10);
    g_exit = true;

    // wait two sub tasks return
    s_task_join(__await__, g_stack0);
    s_task_join(__await__, g_stack1);
}

void loop() {
    
    s_task_init_system();
    main_task(__await__, NULL);

    // turn the LED on always
    digitalWrite(LED_BUILTIN, HIGH);
    while(1);
}

Compatibility list

"s_task" can run as standalone coroutine library, or work with library libuv (compiling with macro USE_LIBUV).

Platform coroutine libuv
Windows ✔️ ✔️
Linux ✔️ ✔️
MacOS ✔️ ✔️
FreeBSD (12.1, x64) ✔️ ✔️
Android ✔️ ✔️
MingW (https://www.msys2.org/) ✔️ ✔️
ARMv6-M (M051, Raspberry Pi Pico) ✔️
ARMv7-M (STM32F103, STM32F302) ✔️
STM8 (STM8S103, STM8L051F3) ✔️
riscv32 (GD32VF103) ✔️
Arduino UNO (AVR MEGA328P) ✔️
Arduino DUE (ATSAM3X8E) ✔️

linux tested on

  • i686 (ubuntu-16.04)
  • x86_64 (centos-8.1)
  • arm (raspiberry 32bit)
  • aarch64 (① raspiberry 64bit, ② ubuntu 14.04 / centos7.6 on huawei Kunpeng920)
  • mipsel (openwrt ucLinux 3.10.14 for MT7628)
  • mips64 (fedora for loongson 3A-4000)
  • ppc64 / ppc64le (centos-7.8.2003 altarch)
  • riscv64 (jslinux)

Build

Posix - Linux / FreeBSD / MacOS / MingW(MSYS2)

git clone https://github.com/xhawk18/s_task.git
cd s_task/build/
cmake .
make

If need cross compiler, please set argument CMAKE_C_COMPILER when calling "cmake ." above, for example --

cmake . -DCMAKE_C_COMPILER=aarch64-linux-gnu-gcc

Others - Windows / STM8 / Cortex-M / Arduino, and more ...

Platform Project Tool chain
Windows build\windows\s_task.sln visual studio 2019
Android build\android\cross_build_arm*.sh android ndk 20, API level 21 (test in termux)
STM8S103 build\stm8s103\Project.eww IAR workbench for STM8
STM8L051F3 build\stm8l05x\Project.eww IAR workbench for STM8
STM32F103 build\stm32f103\arcc\Project.uvproj Keil uVision5
STM32F103 build\stm32f103\gcc\Project.uvproj arm-none-eabi-gcc
STM32F302 build\stm32f302\Project.uvporj Keil uVision5
M051 build\m051\Project.uvporj Keil uVision5
Raspberry Pi Pico build\raspberrypi_pico\CMakeLists.txt pico-sdk
GD32VF103 build\gd32vf103\ VSCode + PlatformIO
ATmega328P build\atmega328p\atmega328p.atsln Atmel Studio 7.0
Arduino UNO
Arduino DUE
build\arduino\arduino.ino Arduino IDE

How to use in your project?

On linux/unix like system, after cmake build, you may get the libraries for your project

  • add libs_task.a to your project
  • #include "s_task.h"
  • build with predefined macro USE_LIBUV

On arduino, copy all source files (*.h, *.c) in folder "include" and "src" into your arduino project's subfolder src/s_task/. Please take a look at the folder structure of "build/arduino/".

On windows or other system, please find the project in folder "build" as the project template.

API

Task

/*
 * Return values -- 
 * For all functions marked by __async__ and hava an int return value, will
 *     return 0 on waiting successfully,
 *     return -1 on waiting cancalled by s_task_cancel_wait() called by other task.
 */

/* Function type for task entrance */
typedef void(*s_task_fn_t)(__async__, void *arg);

/* System initialization (without USE_LIBUV defined) */
void s_task_init_system();

/* System initialization (with USE_LIBUV defined)  */
void s_task_init_uv_system(uv_loop_t *loop);

/* Create a new task */
void s_task_create(void *stack, size_t stack_size, s_task_fn_t entry, void *arg);

/* Wait a task to exit */
int s_task_join(__async__, void *stack);

/* Sleep in milliseconds */
int s_task_msleep(__async__, uint32_t msec);

/* Sleep in seconds */
int s_task_sleep(__async__, uint32_t sec);

/* Yield current task */
void s_task_yield(__async__);

/* Cancel task waiting and make it running */
void s_task_cancel_wait(void* stack);

Chan

/* 
 * macro: Declare the chan variable
 *    name: name of the chan
 *    TYPE: type of element in the chan
 *    count: max count of element buffer in the chan
 */
s_chan_declare(name,TYPE,count);

/* 
 * macro: Initialize the chan (parameters same as what's in s_declare_chan).
 * To make a chan, we need to use "s_chan_declare" and then call "s_chan_init".
 */
s_chan_init(name,TYPE,count);

/* 
 * Put element into chan
 *  return 0 on chan put successfully
 *  return -1 on chan cancelled
 */
int s_chan_put(__async__, s_chan_t *chan, const void *in_object);

/* 
 * Put number of elements into chan
 *  return 0 on chan put successfully
 *  return -1 on chan cancelled
 */
int s_chan_put_n(__async__, s_chan_t *chan, const void *in_object, uint16_t number);

/* 
 * Get element from chan
 *  return 0 on chan get successfully
 *  return -1 on chan cancelled
 */
int s_chan_get(__async__, s_chan_t *chan, void *out_object);

/* 
 * Get number of elements from chan
 *  return 0 on chan get successfully
 *  return -1 on chan cancelled
 */
int s_chan_get_n(__async__, s_chan_t *chan, void *out_object, uint16_t number);

Mutex

/* Initialize a mutex */
void s_mutex_init(s_mutex_t *mutex);

/* Lock the mutex */
int s_mutex_lock(__async__, s_mutex_t *mutex);

/* Unlock the mutex */
void s_mutex_unlock(s_mutex_t *mutex);

Event

/* Initialize a wait event */
void s_event_init(s_event_t *event);

/* Wait event */
int s_event_wait(__async__, s_event_t *event);

/* Set event */
void s_event_set(s_event_t *event);

/* Wait event with timeout */
int s_event_wait_msec(__async__, s_event_t *event, uint32_t msec);

/* Wait event with timeout */
int s_event_wait_sec(__async__, s_event_t *event, uint32_t sec);

Specials on embedded platform

API on embedded platform

Chan for interrupt (for embedded only, STM8/STM32/M051/Arduino)

chan api called in tasks

/* Task puts element into chan and waits interrupt to read the chan */
void s_chan_put__to_irq(__async__, s_chan_t *chan, const void *in_object);

/* Task puts number of elements into chan and waits interrupt to read the chan */
void s_chan_put_n__to_irq(__async__, s_chan_t *chan, const void *in_object, uint16_t number);

/* Task waits interrupt to write the chan and then gets element from chan */
void s_chan_get__from_irq(__async__, s_chan_t *chan, void *out_object);

/* Task waits interrupt to write the chan and then gets number of elements from chan */
void s_chan_get_n__from_irq(__async__, s_chan_t *chan, void *out_object, uint16_t number);

chan api called in interrupt

/*
 * Interrupt writes element into the chan,
 * return number of element was written into chan
 */
uint16_t s_chan_put__in_irq(s_chan_t *chan, const void *in_object);

/*
 * Interrupt writes number of elements into the chan,
 * return number of element was written into chan
 */
uint16_t s_chan_put_n__in_irq(s_chan_t *chan, const void *in_object, uint16_t number);

/*
 * Interrupt reads element from chan,
 * return number of element was read from chan
 */
uint16_t s_chan_get__in_irq(s_chan_t *chan, void *out_object);

/*
 * Interrupt reads number of elements from chan,
 * return number of element was read from chan
 */
uint16_t s_chan_get_n__in_irq(s_chan_t *chan, void *out_object, uint16_t number);

Event for interrupt (for embedded only, STM8/STM32/M051/Arduino)

event api called in tasks

/* 
 * Wait event from irq, disable irq before call this function!
 *   S_IRQ_DISABLE()
 *   ...
 *   s_event_wait__from_irq(...)
 *   ...
 *   S_IRQ_ENABLE()
 */
int s_event_wait__from_irq(__async__, s_event_t *event);

/* 
 * Wait event from irq, disable irq before call this function!
 *   S_IRQ_DISABLE()
 *   ...
 *   s_event_wait_msec__from_irq(...)
 *   ...
 *   S_IRQ_ENABLE()
 */
int s_event_wait_msec__from_irq(__async__, s_event_t *event, uint32_t msec);

/* 
 * Wait event from irq, disable irq before call this function!
 *   S_IRQ_DISABLE()
 *   ...
 *   s_event_wait_sec__from_irq(...)
 *   ...
 *   S_IRQ_ENABLE()
 */
int s_event_wait_sec__from_irq(__async__, s_event_t *event, uint32_t sec);

event api called in interrupt

/* Set event in interrupt */
void s_event_set__in_irq(s_event_t *event);
Low power mode

If there's no code in function "my_on_idle", the program will run in busy wait mode, which may cause CPU 100% occupied. But we can avoid this and support low power mode by adding correct sleeping instructions in function my_on_idle.

Now we have do that on Windows/Linux/MacOS and Android.

On embedded platform without OS, we may not fully implement low power mode. Please check function "my_on_idle" for the corresponding platform if you want to optimize the power consumption.

void my_on_idle(uint64_t max_idle_ms) {
    /* Add code here to make CPU run into sleep mode,
       the maximum sleeping time is "max_idle_ms" milliseconds. */
}

How to make port?

Please find document here

Contact

使用中有任何问题或建议,欢迎QQ加群 567780316 交流。

s_task交流群

VS.

Thanks

s_task's People

Contributors

xhawk18 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  avatar  avatar  avatar  avatar  avatar  avatar

s_task's Issues

编译问题

在linux下编译时遇到了这个问题:cross_build_arm64.sh: 5: cross_build_arm64.sh: Syntax error: "(" unexpected

s_chan_declare 数据段大小问题?

源码 : #define s_chan_declare(name,TYPE,count)
s_chan_t name[1 + ((count)*sizeof(TYPE) + sizeof(s_chan_t) - 1) / sizeof(sizeof(s_chan_t))]

不懂为什么是除以sizeof(sizeof(s_chan_t)), 而不是除以sizeof(s_chan_t)

s_mutux_lock with timeout

it would be good to have a possibility to try to acquire a mutex with a timeout (as is possible for an event).

关于最小stack要求,以及一些代码文档建议

hawk 你好,

最近试用了一下s_task, 感觉很不错, 不过因为对代码的理解有限,还希望能请教几个问题。

char g_stack0[384];    // 64 * 6  
char g_stack1[384];

我测试了一下, 64*5 的stack, 也可以运行, 但64*4 就出现了报错 fprintf(stderr, "stack overflow in lower bits");

if(((int *)real_stack)[0] != S_TASK_STACK_MAGIC) {
#ifndef NDEBUG
            fprintf(stderr, "stack overflow in lower bits");
#endif
            while(1);   /* dead */
        }

这里的 S_TASK_STACK_MAGIC ((int)0x5AA55AA5), 没有看到什么文档, 请问具体是什么含义。

另外,swapcontext 两个注释掉的指令, 请问被注释的原因是什么。

static void swapcontext(ucontext_t *old_tcb, const ucontext_t *new_tcb) {
    __asm__ __volatile__(
        "PUSH    {r4-r12,lr}\n"
        "STR     sp, [r0]\n"
        //"LDR     r2, [r1]\n"
        //"MOV     sp, r2\n"
        "LDR     sp, [r1]\n"
        "POP     {r4-r12,lr}\n"

        "BX      lr\n"
   );
}

最后, 如果关于create_context, 的-1 和 -10 两个常数有相关说明就非常好了。

static void create_context(ucontext_t *oucp, void *stack, size_t stack_size) {
    uint32_t *top_sp;
    uint32_t int_sp;

    int_sp = (int )((char *)stack + stack_size);
    int_sp = int_sp / 4 * 4;  //alignment
    top_sp = (uint32_t *)int_sp;
    
    top_sp[-1] = (int)&s_task_context_entry;
    oucp->sp = (int)&top_sp[-10];
}

建议 build 目录改名

直接在 build 目录里 make 会生成不需要的中间文件。
建议在里面新建下一级目录编译:

cd s_task/build
mkdir build && cd build
cmake -G Ninja ..
ninja

这样的话,build/build 这个目录可以随时删掉,这样一来 s_task/build 这个目录名字就不太合适,容易混淆。

libuv未计算chan等待状态导致程序提前结束

现象为多个send_task发送消息到recv_task,由于chan空间设置太小,导致部分send_task进入等待态,而recv_task在收到第一个消息时正常退出并设置全局变量控制所有send_task退出。此时,剩余send_task不在全局变量的active_task上且由于没有定时器、没有uv的handles和req,uv正常结束流程,导致剩余等待状态的send_task没有正常退出。

不知道这是个bug还是正常使用不会出现这种情况?

s_event_wait always returns 0

I initialize an event, and then wait on it in a task, using
s_event_wait_sec(await, &ev, 1);

This works (execution proceeds when a timeout occurs or the event is set). However, I thought that a timeout would be indicated in the return value. However, it is zero in both cases (timeout or event received). I also do not see where in the code this would be handled.

event变量问题

while (!arg.trigged) {
    ret = s_event_wait(__await__, &arg.event);
    if (ret != 0)
        return ret;
}

请问下,为什么几乎所有的event变量都要这样使用.是因为就算在等待队列中也可能被触发吗.
我想给我的程序connect read 这种操作设置超时 使用 s_event_wait_msec 函数 在没到定时之前被别的地方s_event_set 触发了这个s_event_wait_msec等待的event变量 好像还是存在等待队列中 有没有办法可以直接手动删除这个event 变量

提个问题

我有一个这样的想法
背景大概是安卓ndk编程中,jni_onload这里的这个线程往往是可以直接使用env去做一些java的操作,如果新创建的线程就需要先 AttachCurrentThread 一下才能正常使用 env

如果能在 jni_onload 函数中注册一个协程任务,然后让协程任务本身为一个死循环,同时处于互斥锁卡住的状态,在ndk编程中的其他线程去处理这个互斥锁,同时携带上需要调用的函数地址,这样实现一个在任意线程中都可以通过消息来实现任何函数的调用都在 jni_onload 这个线程中

以上只是一个举例,这里的应用场景还有比如在逆向 il2cpp unity 中,注册在ui线程,跨线程从其他线程都能修改ui

目前看起来的话 s_task 只是在指定的一个位置创建了协程并卡住当前线程,只有等到所有join的协程都执行完了才能继续当前线程,且互斥量尽在协程之间使用有效,外部创建的线程修改互斥量好像并不能继续协程的执行

问题落到了:如何在协程处于阻塞状态的时候从其他线程唤醒协程

    LOGD("JNI_OnLoad -- Start -- ");

    s_task_init_system();

    s_mutex_init(&sMutex);
    s_mutex_lock(__await__, &sMutex);

    s_task_create(g_stack_main,sizeof (g_stack_main),*[](__async__, void *arg)->auto{
        LOGD("s_mutex_lock %p %d",&sMutex,sMutex.locked);
        while(true){
            s_mutex_lock(__await__, &sMutex);
            LOGD("s_task_create run");
        }
    }, nullptr);

    s_task_join(__await__,g_stack_main);

    LOGD("JNI_OnLoad -- End -- ");

// 过了一段时间后,其他线程调用
    s_mutex_unlock(&sMutex);

并不会继续协程的执行

vs libuv

你好!
请问这个工程项目对比libuv有哪些优势?
我想找到一个比libuv性能更好,支持多核cpu的库。主要应用是python的uvloop,一个libuv的包装,单核性能还可以,希望可以充分利用多核,性能再好些更好了。
谢谢

编译运行example1错误

gcc -I s_task/include/ -I s_task/libuv/include/ main.c -o main libs_task.a -lpthread -ldl -lrt

直接链接libs_task.a文件, 运行main出错: Segmentation fault (core dumped)

gdb main, 提示:
Program received signal SIGSEGV, Segmentation fault.
0x00000000004069a1 in uv_timer_init ()

用linux下的build.sh文件编译运行没有问题...

ex4_echo_server.c 客户端关闭再连接崩溃问题?

在 void on_connection(uv_stream_t *stream, int status) 中已经对client对象初始化了:
s_list_init(&client->list_node);
s_list_attach(&server->running_clients, &client->list_node);
在 static void run_client(async, void *arg) 中再次初始化:
s_list_init(&client->list_node);
s_list_attach(&client->server->running_clients, &client->list_node);
重复初始化存在问题.

小建议

针对 void s_task_create(void* stack, size_t stack_size, s_task_fn_t task_entry, void* task_arg) 提出的小建议

可以把 前两个参数稍微封装一下,抽象成 TaskID (实际上就是一个创建的栈指针),大小不用一次性初始化写死,也可以借鉴 java ArrayList 的动态大小分配动态扩容 stack,至于扩容操作可能导致栈指针的变化

可以使用二级指针来解决问题
typedef void** Task;

或者是:
typedef struct
{
    int ID;
    void* statck;
    size_t stack_size;
} TaskID;

再或者使用 map<int,void*> TaskID

看起来简洁一些,让暴露出来的API变成

void s_task_create(TaskID* taskID, s_task_fn_t task_entry, void* task_arg)

后面的 join 等api 对应使用过方式也都由 taskid 接管

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.