Git Product home page Git Product logo

event-loop's Introduction

Revolt   

Revolt is a rock-solid event loop for concurrent PHP applications. The usual PHP application spends most of its time waiting for I/O. While PHP is single threaded, cooperative multitasking can be used to allow for concurrency by using the waiting time to do different things.

PHP's traditional synchronous execution flow is easy to understand. Doing one thing at a time. If you query a database, you send the query and wait for a response from the database server. Once you have the response, you can start doing the next thing.

Amp, ReactPHP, and other libraries have offered cooperative multitasking in PHP for a long time. However, their event-driven nature was incompatible to many existing interfaces and required a different thinking model. PHP 8.1 ships with fibers built-in, which offers cooperative multi-threading. Calls can be synchronous without promises or callbacks, while still allowing for non-blocking I/O.

Every application making use of cooperative multitasking needs a single scheduler (also called event loop), which this package provides. Revolt is the result of combining years of experience of Amp's and ReactPHP's event loop implementations. However, it is not a full-blown framework for writing concurrent PHP applications, but only provides what's necessary as a common base. Different (strongly) opinionated libraries can be built on top of it and both Amp and ReactPHP will continue to co-exist.

Installation

It may surprise people to learn that the PHP standard library already has everything we need to write event-driven and non-blocking applications. This package can be installed as a Composer dependency on PHP 8.1 and later.

composer require revolt/event-loop

Applications with many concurrent file descriptors require one of the extensions.

→  View documentation
→  View examples

Versioning

revolt/event-loop follows the semver semantic versioning specification.

License

The MIT License (MIT). Please see LICENSE file for more information.

event-loop's People

Contributors

azjezz avatar bwoebi avatar danog avatar kelunik avatar nevay avatar nhedger avatar simpod avatar tnsoftbear avatar trowski avatar xjaja 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

event-loop's Issues

Bad file descriptor with "ev" driver when trying to listen on socket (Windows)

This code:

$socket = \Amp\Socket\listen('tcp://127.0.0.1:49000', null);
$socket->accept();

Throws this warning on Windows platform:

Warning: EvLoop::run(): Libev error(9): Bad file descriptor in ...\vendor\revolt\event-loop\src\EventLoop\Driver\EvDriver.php on line 164

php -v

PHP 8.1.1 (cli) (built: Dec 15 2021 10:36:13) (NTS Visual C++ 2019 x64)
Copyright (c) The PHP Group
Zend Engine v4.1.1, Copyright (c) Zend Technologies

php --ri ev

ev

Ev support => enabled
Debug support => disabled
Version => 1.1.6RC1

Package versions:

revolt/event-loop: v0.1.1
amphp/socket: v2.x-dev

Suspension safetly of error handler

We made all callbacks suspension-safe by running them in an extra fiber, but this isn't true for the error handler currently. This has high potential for mistakes, so we should do something about it.

unexpected error with extension drivers ( uv / ev )

The following code samples fail with different error when using with ev and uv drivers.

Stream select driver does not fail.

using PSL 2.0.x branch:

<?php

require_once 'vendor/autoload.php';

Psl\Async\concurrent([
    fn() => Psl\Async\sleep(0.1),
    fn() => Psl\Async\sleep(0.1),
]);

using Amphp v3 branch:

<?php

require_once 'vendor/autoload.php';

Amp\Future\all([
  Amp\coroutine(fn() => null),
  Amp\coroutine(fn() => null),
]);

Expected: no error.


Actual result:

EvDriver :

[*] ev extension is enabled.
> php sample.php
PHP Warning:  EvLoop::run(): Stopping event loop because of uncaught exception in the callback in /home/azjezz/Projects/php-standard-library/vendor/revolt/event-loop/src/EventLoop/Driver/EvDriver.php on line 164

Warning: EvLoop::run(): Stopping event loop because of uncaught exception in the callback in /home/azjezz/Projects/php-standard-library/vendor/revolt/event-loop/src/EventLoop/Driver/EvDriver.php on line 164

UvDriver :

[*] uv extension is enabled.
> php sample.php
[1]    1478 segmentation fault  php sample.php

Note: EventDriver used to fail as well, however, 70e67dc fixed it.

Segfault question

I'm getting some segfaults reported on older PHP versions (< 8.1.17 and <8.2.4) in our GrumPHP project.
Would it make sense to add a conflict section in the composer file of this package so that people know there something wrong with their setup?

It's a PHP bug but it make usage of fibers rather unstable:

  • GH-10496 (segfault when garbage collector is invoked inside of fiber).

I can always add it to my project, but it might make more sense to add it here so that other projects can benifit from this as well.

rdkafka transport layering [Kafka <-> WebSocket]

Would it be possible to use this library to maintain a connection to to a 3rd party API and an internal kafka broker using the rdkafka extension

We have a microservice design with many apps integrated with kafka, and we are working on rewriting our connector to a 3rd party service. We are a bit lost though on how we would be able to maintain a dual connection to both their 3rd party API (via websocket) and an internal kafka broker to send and received both ways.

How to measure an event loop overhead?

I'm writing a pure PHP equivalent of libpq that uses RevoltPHP v0.2.1 and Amp v3 (latest beta).
And after I implemented the basic things like sending queries/reading rows, I decided to profile the performance.

And then I saw that the most time is spent on calling Fiber::suspend:
image

If I understand correctly, Fiber::suspend time includes I/O wait time.
But is there any way to see only I/O wait time?

When I tried to compare my performance results with the same bench, but using the pgsql PHP extension, it was almost twice as fast.

A problem in Windows: `StreamSelectDriver::$streamSelectErrorHandler`

Issue

In Windows, when I type ctrl+c, an exception is thrown.
There are no problems under the Linux system.
I don't know why. Please help me to see if this is a bug?
Please look at my code. I used the error suppressor to call mkdir, which should not throw an exception.

$this->streamSelectErrorHandler = function (int $errno, string $message): void {

I think a judgment error report can be added to StreamSelectDriver::$streamSelectErrorHandler
if ((error_reporting() & $errno) == 0)return;

I can solve this problem in my code, but I don't know why register_shutdown_function executed in the event loop.

My English is very poor. Please look at the code. Thank you

PHP code

<?php

require __DIR__ . '/vendor/autoload.php';

use Revolt\EventLoop;

$server = \stream_socket_server('tcp://0.0.0.0:8080');
\stream_set_blocking($server, false);

EventLoop::onReadable($server, fn () => null);

\register_shutdown_function(function () {
    @mkdir(__FILE__); // I deliberately made an error, but I masked it through the error suppressor.
});

if (DIRECTORY_SEPARATOR === '\\') {
    sapi_windows_set_ctrl_handler(function () {
        echo "ctrl+c." . PHP_EOL;
        exit;
    });
} else {
    if (defined('\SIGINT')) {
        EventLoop::onSignal(\SIGINT, function () {
            echo "ctrl+c." . PHP_EOL;
            exit;
        });
    }
}

EventLoop::repeat(1, fn () => null);

echo "started." . PHP_EOL;

EventLoop::run();

Windows system threw an exception

  • Windows 11
  • PHP 8.2.0RC5
F:\revolt>php test.php
started.
ctrl+c.
PHP Fatal error:  Uncaught Exception: mkdir(): File exists in F:\revolt\vendor\revolt\event-loop\src\EventLoop\Driver\StreamSelectDriver.php:77
Stack trace:
#0 [internal function]: Revolt\EventLoop\Driver\StreamSelectDriver->Revolt\EventLoop\Driver\{closure}(2, 'mkdir(): File e...', 'F:\\revolt\\test....', 14)
#1 F:\revolt\test.php(14): mkdir('F:\\revolt\\test....')
#2 [internal function]: {closure}()
#3 {main}
  thrown in F:\revolt\vendor\revolt\event-loop\src\EventLoop\Driver\StreamSelectDriver.php on line 77

Linux system is OK

tianyiw@TIANYIW-HOME-PC:~/revolt$ php test.php
started.
^Cctrl+c.

[Feedback] Welcome to RevoltPhp 🥳

Hi there 👋 😄 ,
I'm Benoit, an enthusiast Php/Async dev, and I just wanted to give my feedback (+ questions) as proposed in this tweet.

First of all, thank you very much for all this work! Even if I have some comments about RevoltPhp design, I'm really convinced that it's an awesome opportunity for Php ecosystem to see ReactPhp & Amp teams (& friends) working together to build such library 👍 🤩

Some context

When I had to choose an EventLoop for my company internal project, I studied a lot ReactPhp and Amp developer experience (DX). I really like the EventLoop Interface in ReactPhp, but I was really concerned by the potential callback hell for my team. In the other hand, Amp use generators with a nice DX (more intuitive to read IMHO), but EventLoop is global and doesn't expose an interface (but driver does). That's why we created Tornado, a modest attempt to abstract both with the Generator approach… So, when I read the first tweet of RevoltPhp I was very curious 😄

Fibers

I'm really excited about Fibers, I think it's the perfect tool to provide a fluent DX for async processing. But surprisingly, RevoltPhp seems to use callbacks extensively… Do you consider RevoltPhp like a very low-level async layer on which we can build some Fiber-oriented libs/projects?
Nevertheless, it seems possible to rely on Fiber with the Suspension concept (example), but it seems very verbose.

$watcher = EventLoop::onWritable($stream, fn () => $suspension->resume(null));
$suspension->suspend();
EventLoop::cancel($watcher);

I was maybe expecting something like this:

EventLoop::waitWritable($stream);

Or maybe keeping the concept of Promise/Future, with something like:

$foo = EventLoop::wait(EventLoop::async(foo(...)));

🤷
Anyway, all these functions can be created from Revolt core functions, it's just to give you my (humble) opinion 😃

Combinators

Do you intend to provide some basic functions to combine asynchronous functions? Like all, race… ?
Once again, it's totally possible thanks to suspension (example), but it's not straightforward IMHO

Conclusion

That's all 😄 Of course, I would prefer to have a non-global EventLoop or an EventLoopInterface (instead of using Drivers), but I don't think it's the most important, just a matter of taste 😉
And once again, thank you for this library 👍 Even with this design, I'm pretty sure it will be very useful as a core component to other projects.
If you are curious, I created a 1-file async-library for an incoming talk, to quickly show possibilities offered by Fibers: https://github.com/b-viguier/Slip . Just for demonstration purpose, but feel free to comment 🤷

Benoit

Exceptions for closed resources

Drivers result in different exceptions/errors making it harder for end-user to handle.

an example would be EventLoop::onReadable/EventLoop::onWritable throwing different exception/error depending on the driver in case the resource was closed at one point, instead, we should throw ClosedResourceException.

currently, I'm handling this case by catching Throwable, and checking if the resource was closed again: https://github.com/azjezz/psl/blob/2.0.x/src/Psl/Async/await_readable.php#L42-L54, but this method is not sufficient.

Revolt w/Amphp or wait for new framework?

After a while of studying and testing Amphp, I'm finally comfortable programing in Amp. Then I learned about Revolt and its use of Fibers to simplify concurrent php programming. The question/dilemma I have is which should I use now? I would prefer to use Revolt so I don't have to do all of the yield/generator/call() mental juggling but I love the existing framework/libraries that Amp already provides.

So do I implement my own higher-level libs to use Revolt or is there a way I can use Revolt in conjunction with Amp or do I have to wait for another Revolt-based framework to be built? Either way this is very exciting stuff.

Fiber stack protect failed

I catch error in my production server in callback method set_exception_handler(***):

Fiber stack protect failed: mprotect failed: Cannot allocate memory (12), /project/vendor/revolt/event-loop/src/EventLoop/Internal/AbstractDriver.php:495

In php.ini:

; Maximum amount of memory a script may consume
; https://php.net/memory-limit
memory_limit = -1

Available memory in server 230Gb.

apr_socket_recv: Connection reset by peer (104)

Hello maintainer, I try http-server example in https://github.com/revoltphp/event-loop/blob/main/examples/http-server.php,

But when using ab tool to benchmark, it result in

This is ApacheBench, Version 2.3 <$Revision: 1901567 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/

Benchmarking 127.0.0.1 (be patient)
apr_socket_recv: Connection reset by peer (104)

AFAIK, this is not the problem with ab tool it self, but with the webserver, I'm using default ubuntu server to test it.

Add additional inspection tools

Currently there's getInfo to allow for some statistics, but I think it would be better to add further inspection tools for monitoring / debugging, e.g.

getCallbackIds(): array
getCallback(string): callable
isReferenced(string): bool
isEnabled(string): bool
getType(string): string // not sure which name fits here

This would probably also allow removing getInfo().

PSR-3 LoggingDriver to trace all operations?

I have some weird issue in amphp/http-client that only happens with extension drivers where it doesn't trigger the readability callback and then eventually triggers a timeout delay callback, however when reading inside the timeout, there's still data to read.

I added some print based logging for debugging purposes, but I guess it could be helpful to have a logging decorator.

Resumptions in final cycle collection after EventLoop return in DriverSuspension are ignored

Consider these test cases (the one case immediately resumes in cycle collection, the other schedules something on the event loop):

    public function testSuspensionResumptionWithQueueInGarbageCollection(): void
    {
        $suspension = EventLoop::getSuspension();

        $class = new class($suspension) {
            public function __construct(public Suspension $suspension) {}
            public function __destruct() { $this->suspension->resume(true); }
        };
        $cycle = [$class, &$cycle];
        unset($class, $cycle);

        $ended = $suspension->suspend();

        $this->assertTrue($ended);
    }

    public function testEventLoopResumptionWithQueueInGarbageCollection(): void
    {
        $suspension = EventLoop::getSuspension();

        $class = new class($suspension) {
            public function __construct(public Suspension $suspension) {}
            public function __destruct() { EventLoop::queue($this->suspension->resume(...), true); }
        };
        $cycle = [$class, &$cycle];
        unset($class, $cycle);

        $ended = $suspension->suspend();

        $this->assertTrue($ended);
    }

These cases both will fail with:

Error: Event loop terminated without resuming the current suspension (the cause is either a fiber deadlock, or an incorrectly unreferenced/canceled watcher):

(The trailing colon is also weird, it means that no other fibers exist, maybe write that out.)

I was initially very confused when this happened in my code as dumping the event loop showed a pending queue() call, while the EventLoop reported as finished.

I would expect that, after the gc_collect_cycles():

\gc_collect_cycles(); // Collect any circular references before dumping pending suspensions.

the EventLoop is checked for any active, referenced watchers or microtasks, and if yes, is resumed. Similarly, if a suspension was resumed within that cycle collection, it will leave the code path leading to the error being thrown.

how to safe close stream?

Hello,

When i use examples the http-server.

In this code

if ($written === \strlen($data)) {
\fclose($conn);
EventLoop::cancel($watcher);
} else {

When execute close conn, it will be throw TypeError: stream_select(): supplied resource is not a valid stream resourcs

I see the EventLoop::cancel($watcher); in following, the deactivate function seem never to be run?

if ($callback instanceof DeferCallback) {
// Callback was only queued to be enabled.
unset($this->enableDeferQueue[$id]);
} elseif (isset($this->enableQueue[$id])) {
// Callback was only queued to be enabled.
unset($this->enableQueue[$id]);
} else {
$this->deactivate($callback);
}

Memory leak if callbacks suspend

reproduce:

  1. create script.php with the following content:
<?php

declare(strict_types=1);

use Revolt\EventLoop;

require __DIR__ . '/../../vendor/autoload.php';

$write_line = static fn(string $m, ...$args) => printf($m . "\n", ...$args);

$write_line('Server is listening on http://localhost:3030');

// Error reporting suppressed since stream_socket_server() emits an E_WARNING on failure (checked below).
$server = @stream_socket_server('tcp://localhost:3030', $errno, $_, flags: STREAM_SERVER_BIND | STREAM_SERVER_LISTEN, context: stream_context_create([
    'socket' => [
        'ipv6_v6only' => true,
        'so_reuseaddr' => false,
        'so_reuseport' => false,
        'so_broadcast' => false,
        'tcp_nodelay' => false,
    ]
]));
if (!$server || $errno) {
    throw new RuntimeException('Failed to listen localhost 3030.', $errno);
}

$watcher = null;
EventLoop::unreference(EventLoop::onSignal(SIGINT, static function () use ($server, &$watcher) {
    EventLoop::cancel((string) $watcher);
    fclose($server);
}));

$watcher = EventLoop::onReadable($server, function ($watcher, $resource) {
    $stream = @stream_socket_accept($resource, timeout: 0.0);
    if (false === $stream) {
        EventLoop::cancel($watcher);

        return;
    }

    stream_set_read_buffer($stream, 0);
    stream_set_blocking($stream, false);
    $suspension = EventLoop::createSuspension();
    $watcher = EventLoop::onReadable($stream, function () use ($suspension) {
        $suspension->resume();
    });
    $suspension->suspend();
    EventLoop::cancel($watcher);
    $request = stream_get_contents($stream);
    $suspension = EventLoop::createSuspension();
    $watcher = EventLoop::onWritable($stream, function () use ($suspension) {
        $suspension->resume();
    });
    $suspension->suspend();
    EventLoop::cancel($watcher);
    fwrite($stream, "HTTP/1.1 200 OK\n");
    $suspension = EventLoop::createSuspension();
    $watcher = EventLoop::onWritable($stream, function () use ($suspension) {
        $suspension->resume();
    });
    $suspension->suspend();
    EventLoop::cancel($watcher);
    fwrite($stream, "Server: TCP Server\n");
    $suspension = EventLoop::createSuspension();
    $watcher = EventLoop::onWritable($stream, function () use ($suspension) {
        $suspension->resume();
    });
    $suspension->suspend();
    EventLoop::cancel($watcher);
    fwrite($stream, "Connection: close\n");
    $suspension = EventLoop::createSuspension();
    $watcher = EventLoop::onWritable($stream, function () use ($suspension) {
        $suspension->resume();
    });
    $suspension->suspend();
    EventLoop::cancel($watcher);
    fwrite($stream, "Content-Type: text/html; charset=utf-8\n\n");
    $suspension = EventLoop::createSuspension();
    $watcher = EventLoop::onWritable($stream, function () use ($suspension) {
        $suspension->resume();
    });
    $suspension->suspend();
    EventLoop::cancel($watcher);
    fwrite($stream, "<h3>Hello, World!</h3>");
    $suspension = EventLoop::createSuspension();
    $watcher = EventLoop::onWritable($stream, function () use ($suspension) {
        $suspension->resume();
    });
    $suspension->suspend();
    EventLoop::cancel($watcher);
    fwrite($stream, "<pre><code>" . htmlentities($request) . "</code></pre>");
    $suspension = EventLoop::createSuspension();
    $watcher = EventLoop::onWritable($stream, function () use ($suspension) {
        $suspension->resume();
    });
    $suspension->suspend();
    EventLoop::cancel($watcher);
    fwrite($stream, sprintf('memory usage: %dMiB<br />', round(memory_get_usage() / 1024 / 1024, 1)));
    $suspension = EventLoop::createSuspension();
    $watcher = EventLoop::onWritable($stream, function () use ($suspension) {
        $suspension->resume();
    });
    $suspension->suspend();
    EventLoop::cancel($watcher);
    fwrite($stream, sprintf('peak memory usage: %dMiB<br />', round(\memory_get_peak_usage() / 1024 / 1024, 1)));
    @fclose($stream);
});

EventLoop::run();

$write_line('');
$write_line('Goodbye 👋');
  1. run php script.php

memory keeps going up with each request ( noticeable with a request batch of 10k )

It seems suspensions are not being destructed properly and taking space in memory?

I'm not really sure what is happening, i first noticed the leak in https://github.com/azjezz/hack-php-async-io/blob/main/src/server.php, and went on simplifying the code to isolate it, until i reached this step, where no PSL code was involved, which means the leak is happening in revolt, or I'm doing something wrong.

Event loop gets completely stuck with uv backend

Using the reproducer @ https://paste.daniil.it/uv.tar.xz (https://github.com/danog/MadelineProto @ commit 0e838c79a56f160143ad88f0dd65d64dc102d2c4 + an aaa.php test file), running the following command on arch linux yields a total hang of the entire application around once every 10 tries, without even the periodic "Still alive" message, manually set up using EventLoop::repeat(1.0, fn () => var_dump("Still alive")); in aaa.php.

rm -rf aaa.madeline; php aaa.php

Sending CTRL-C unblocks the application, gdb shows it's stuck on an epoll syscall inside uv.

Sometimes a partial hang occurs (i.e. reads on some sockets stop returning anything, but the periodic messages are still printed), other times a full hang occurs.

Mimic Octane::concurrently behavior

Hello,

In Laravel Octane when using Swoole its possible to do something like the following.

	[ $recs1, $recs2 ] = Octane::concurrently( [
		fn() => $this->get_recs_a( $ids ),
		fn() => $this->get_recs_b( $ids )
	] );

What this will do is execute both functions get_recs_a and get_recs_b concurrently yet not returning unless both got executed and set their return values to $recs1 and $recs2.

How to achieve similar behavior?

Remove Revolt\now

We only have Revolt\now() left in functions.php. It doesn't seem to provide much value and we should get rid of it, so we don't need any files autoload.

Consider documenting a security policy

The Drupal framework is looking to adopt Revolt as its event loop. Since this would become a very integral part of Drupal (with some existing systems already moving to async), one of the evaluation criteria is that the package has a security policy that outlines how to properly report security issues.

From one of Drupal's security team members on the issue discussing Revolt's adoption:

Can we ask the maintainers if they are willing to publish a security policy? Given that this is a low level runtime dependency it seems quite important that if there is a security issue the maintainers are prepared to fix it within a reasonable timescale.

Would it be possible to clarify Revolt's security policy and document it in a SECURITY.md file so it shows up in GitHub's security tab? This can also help other larger frameworks in adopting Revolt :)

Feature Request: Provide a way to find out if a calbackId is valid

Problem Description

I want to run code like the following:

// Perform eager tasks as often as possible but never more than one at a time.
$callbackId = EventLoop::repeat(0, function ($callbackId) {
  EventLoop::disable($callbackId);
  $this->performHalfSecondTask();
  EventLoop::enable($callbackId);
});

// Simulate some long task that mostly suspends.
EventLoop::delay(1.25, function() use ($callbackId) {
  echo "Cancelling repeat\n";
  EventLoop::cancel($callbackId);
});

EventLoop::run();

The problem with this is that there's no efficient way to check if $callbackId is still valid when running EventLoop::enable($callbackId); which means that there's a guaranteed exception somewhere in the program.

Workarounds

There's two possible ways around this, neither of which are great:

  1. Check all the identifiers, this is potentially expensive if the list of identifiers is large due to a high number of async tasks.
if (in_array($callbackId, EventLoop::getIdentifiers, TRUE)) {}
  1. Catch the exception. This feels quite verbose and it's unclear whether this is intentional or whether a developer unintentionally hides a bug here.
try {
    EventLoop::enable($callbackId);
}
catch (EventLoop\InvalidCallbackError $e) {}

Feature Request

It would be great if there is a method saying EventLoop::isValidIdentifier which does an isset behind the scenes.

if (EventLoop::isValidIdentifier($callbackId)) {
   EventLoop::enable($callbackId);
}

Alternatively a second parameter to enable/disable to indicate it may fail silently would work too:

EventLoop::enable($callbackId, TRUE);

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.