Giter Site home page Giter Site logo

hot-lib-reloader-rs's Introduction

hot-lib-reloader

Crates.io CI License

hot-lib-reloader is a development tool that allows you to reload functions of a running Rust program. This allows to do "live programming" where you modify code and immediately see the effects in your running program.

This is build around the libloading crate and will require you to put code you want to hot-reload inside a Rust library (dylib). For a detailed discussion about the idea and implementation see this blog post.

For a demo and explanation see also this Rust and Tell presentation.

Table of contents:

Usage

To quicky generate a new project supporting hot-reload you can use a cargo generate template: cargo generate rksm/rust-hot-reload.

Prerequisites

macOS

On macOS the reloadable library needs to get codesigned. For this purpose, hot-lib-reloader will try to use the codesign binary that is part of the XCode command line tools. It is recommended to make sure those are installed.

Other platforms

It should work out of the box.

Example project setup

Assuming you use a workspace project with the following layout:

├── Cargo.toml
└── src
│   └── main.rs
└── lib
    ├── Cargo.toml
    └── src
        └── lib.rs

Executable

Setup the workspace with a root project named bin in ./Cargo.toml:

[workspace]
resolver = "2"
members = ["lib"]

[package]
name = "bin"
version = "0.1.0"
edition = "2021"

[dependencies]
hot-lib-reloader = "^0.6"
lib = { path = "lib" }

In ./src/main.rs define a sub-module using the [hot_lib_reloader_macro::hot_module] attribute macro which wraps the functions exported by the library:

// The value of `dylib = "..."` should be the library containing the hot-reloadable functions
// It should normally be the crate name of your sub-crate.
#[hot_lib_reloader::hot_module(dylib = "lib")]
mod hot_lib {
    // Reads public no_mangle functions from lib.rs and  generates hot-reloadable
    // wrapper functions with the same signature inside this module.
    // Note that this path relative to the project root (or absolute)
    hot_functions_from_file!("lib/src/lib.rs");

    // Because we generate functions with the exact same signatures,
    // we need to import types used
    pub use lib::State;
}

fn main() {
    let mut state = hot_lib::State { counter: 0 };
    // Running in a loop so you can modify the code and see the effects
    loop {
        hot_lib::step(&mut state);
        std::thread::sleep(std::time::Duration::from_secs(1));
    }
}

Library

The library should expose functions. It should set the crate type dylib in ./lib/Cargo.toml:

[package]
name = "lib"
version = "0.1.0"
edition = "2021"

[lib]
crate-type = ["rlib", "dylib"]

The functions you want to be reloadable should be public and have the #[no_mangle] attribute. Note that you can define other function that are not supposed to change without no_mangle and you will be able to use those alongside the other functions.

pub struct State {
    pub counter: usize,
}

#[no_mangle]
pub fn step(state: &mut State) {
    state.counter += 1;
    println!("doing stuff in iteration {}", state.counter);
}

Running it

  1. Start compilation of the library: cargo watch -w lib -x 'build -p lib'
  2. In another terminal run the executable: cargo run

Now change for example the print statement in lib/lib.rs and see the effect on the runtime.

In addition, using a tool like cargo runcc is recommended. This allows to run both the lib build and the application in one go.

lib-reload events

LibReloadObserver

You can get notified about two kinds of events using the methods provided by [LibReloadObserver]:

  • wait_for_about_to_reload the watched library is about to be reloaded (but the old version is still loaded)
  • wait_for_reload a new version of the watched library was just reloaded

This is useful to run code before and / or after library updates. One use case is to serialize and then deserialize state another one is driving the application.

To continue with the example above, let's say instead of running the library function step every second we only want to re-run it when the library has changed. In order to do that, we first need to get hold of the LibReloadObserver. For that we can expose a function subscribe() that is annotated with the #[lib_change_subscription] (that attribute tells the hot_module macro to provide an implementation for it):

#[hot_lib_reloader::hot_module(dylib = "lib")]
mod hot_lib {
    /* code from above */

    // expose a type to subscribe to lib load events
    #[lib_change_subscription]
    pub fn subscribe() -> hot_lib_reloader::LibReloadObserver {}
}

And then the main function just waits for reloaded events:

fn main() {
    let mut state = hot_lib::State { counter: 0 };
    let lib_observer = hot_lib::subscribe();
    loop {
        hot_lib::step(&mut state);
        // blocks until lib was reloaded
        lib_observer.wait_for_reload();
    }
}

How to block reload to do serialization / deserialization is shown in the reload-events example.

was_updated flag

To just figure out if the library has changed, a simple test function can be exposed:

#[hot_lib_reloader::hot_module(dylib = "lib")]
mod hot_lib {
    /* ... */
    #[lib_updated]
    pub fn was_updated() -> bool {}
}

hot_lib::was_updated() will return true the first time it is called after the library was reloaded. It will then return false until another reload occurred.

Usage tips

Know the limitations

Reloading code from dynamic libraries comes with a number of caveats which are discussed in some detail here.

No signature changes

When the signature of a hot-reloadable function changes, the parameter and result types the executable expects differ from what the library provides. In that case you'll likely see a crash.

Type changes require some care

Types of structs and enums that are used in both the executable and library cannot be freely changed. If the layout of types differs you run into undefined behavior which will likely result in a crash.

See use serialization for a way around it.

Hot-reloadable functions cannot be generic

Since #[no_mangle] does not support generics, generic functions can't be named / found in the library.

Global state in reloadable code

If your hot-reload library contains global state (or depends on a library that does), you will need to re-initialize it after reload. This can be a problem with libraries that hide the global state from the user. If you need to use global state, keep it inside the executable and pass it into the reloadable functions if possible.

Note also that "global state" is more than just global variables. As noted in this issue, crates relying on the TypeId of a type (like most ECS systems do) will expect the type/id mapping to be constant. After reloading, types will have different ids, however, which makes (de)serialization more challenging.

Use feature flags to switch between hot-reload and static code

See the reload-feature example for a complete project.

Cargo allows to specify optional dependencies and conditional compilation through feature flags. When you define a feature like this

[features]
default = []
reload = ["dep:hot-lib-reloader"]

[dependencies]
hot-lib-reloader = { version = "^0.6", optional = true }

and then conditionally use either the normal or the hot module in the code calling the reloadable functions you can seamlessly switch between a static and hot-reloadable version of your application:

#[cfg(feature = "reload")]
use hot_lib::*;
#[cfg(not(feature = "reload"))]
use lib::*;

#[cfg(feature = "reload")]
#[hot_lib_reloader::hot_module(dylib = "lib")]
mod hot_lib { /*...*/ }

To run the static version just use cargo run the hot reloadable variant with cargo run --features reload.

Disable #[no-mangle] in release mode

To not pay a penalty for exposing functions using #[no_mangle] in release mode where everything is statically compiled (see previous tip) and no functions need to be exported, you can use the no-mangle-if-debug attribute macro. It will conditionally disable name mangling, depending on wether you build release or debug mode.

Use serialization or generic values for changing types

If you want to iterate on state while developing you have the option to serialize it. If you use a generic value representation such as serde_json::Value, you don't need string or binary formats and typically don't even need to clone anything.

Here is an example where we crate a state container that has an inner serde_json::Value:

#[hot_lib_reloader::hot_module(dylib = "lib")]
mod hot_lib {
    pub use lib::State;
    hot_functions_from_file!("lib/src/lib.rs");
}

fn main() {
    let mut state = hot_lib::State {
        inner: serde_json::json!(null),
    };

    loop {
        state = hot_lib::step(state);
        std::thread::sleep(std::time::Duration::from_secs(1));
    }
}

In the library we are now able to change the value and type layout of InnerState as we wish:

#[derive(Debug)]
pub struct State {
    pub inner: serde_json::Value,
}

#[derive(serde::Deserialize, serde::Serialize)]
struct InnerState {}

#[no_mangle]
pub fn step(state: State) -> State {
    let inner: InnerState = serde_json::from_value(state.inner).unwrap_or(InnerState {});

    // You can modify the InnerState layout freely and state.inner value here freely!

    State {
        inner: serde_json::to_value(inner).unwrap(),
    }
}

Alternatively you can also do the serialization just before the lib is to be reloaded and deserialize immediately thereafter. This is shown in the reload-events example.

Use a hot-reload friendly app structure

Whether or not hot-reload is easy to use depends on how you architect your app. In particular, the "functional core, imparative shell" pattern makes it easy to split state and behavior and works well with hot-lib-reloader

For example, for a simple game where you have the main loop in your control, setting up the outer state in the main function and then passing it into a fn update(state: &mut State) and a fn render(state: &State) is a straightforward way to get two hot-reloadable functions.

But even when using a framework that takes control, chances are that there are ways to have it call hot-reloadable code. The bevy example where system functions can be made hot-reloadable, shows how this can work. See the egui and tokio examples possible setupts.

Adjust the file watch debounce duration

The hot_module macro allows setting the file_watch_debounce attribute which defines the debounce duration for file changes in milliseconds. This is 500ms by default. If you see multiple updates triggered for one recompile (can happen the library is very large), increase that value. You can try to decrease it for faster reloads. With small libraries / fast hardware 50ms or 20ms should work fine.

#[hot_module(dylib = "lib", file_watch_debounce = 50)]
/* ... */

Change the name and location of the dylib file

By default hot-lib-reloader assumes that there will be a dynamic library available in the $CARGO_MANIFEST_DIR/target/debug/ or $CARGO_MANIFEST_DIR/target/release folder, depending on whether the debug or release profile is used. The name of the library is defined by the dylib = "..." portion of the #[hot_module(...)] macro. So by specifying #[hot_module(dylib = "lib")] and building with debug settings, hot-lib-reloader will try to load a target/debug/liblib.dylib on MacOS, a target/debug/liblib.so on Linux or a target/debug/lib.dll on Windows.

If the library should be loaded from a different location you can specify this by setting the lib_dir attribute like:

#[hot_lib_reloader::hot_module(
    dylib = "lib",
    lib_dir = concat!(env!("CARGO_MANIFEST_DIR"), "/target/debug")
)]
mod hot_lib {
    /* ... */
}

Adjust the dylib filename

The hot_module macro allows setting the shadow file name using the loaded_lib_name_template parameter. This is useful when multiple processes are trying to hot reload the same library and can be used to prevent conflicts. This attribute allows for placeholders that can be dynamically replaced:

Placeholder Description Feature Flag
{lib_name} Name of the library as defined in your code None
{load_counter} Incremental counter for each hot reload None
{pid} Process ID of the running application None
{uuid} A UUID v4 string uuid

If you don't specify the loaded_lib_name_template parameter, a default naming convention is used for the shadow filename. This default pattern is: {lib_name}-hot-{load_counter}.

#[hot_lib_reloader::hot_module(
    dylib = "lib",
    // Might result in the following shadow file lib_hot_2644_0_5e659d6e-b78c-4682-9cdd-b8a0cd3e8fc6.dll
    // Requires the 'uuid' feature flags for the {uuid} placeholder
    loaded_lib_name_template = "{lib_name}_hot_{pid}_{load_counter}_{uuid}"
)]
mod hot_lib {
    /* ... */
}

Debugging

If your hot_module gives you a strange compilation error, try cargo expand to see what code is generated.

By default the hot-lib-reloader crate won't write to stdout or stderr but it logs what it does with info, debug, and trace log levels using the log crate. Depending on what logging framework you use (e.g. env_logger), you can enable those logs by setting a RUST_LOG filter like RUST_LOG=hot_lib_reloader=trace.

Examples

Examples can be found at rksm/hot-lib-reloader-rs/examples.

  • minimal: Bare-bones setup.
  • reload-feature: Use a feature to switch between dynamic and static version.
  • serialized-state: Shows an option to allow to modify types and state freely.
  • reload-events: How to block reload to do serialization / deserialization.
  • all-options: All options the hot_module macro accepts.
  • bevy: Shows how to hot-reload bevy systems.
  • nannou: Interactive generative art with nannou.
  • egui: How to hot-reload a native egui / eframe app.
  • iced: How to hot-reload an iced app.

Known issues

tracing crate

When used with the tracing crate multiple issues can occur:

  • When tracing is used in the library that is reloaded the app sometimes crashes with Attempted to register a DefaultCallsite that already exists!
  • When used in combination with bevy, commands.insert(component) operations stop to work after a reload, likely because of internal state getting messed up.

If you can, don't use hot-lib-reloader in combination with tracing.

License

MIT

License: MIT

hot-lib-reloader-rs's People

Contributors

cryomyst avatar izissise avatar lucatrv avatar rksm 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

hot-lib-reloader-rs's Issues

`Iced` sample code keeps crashing at every `lib` reload

I've been experimenting with the following Iced sample code, but it keeps crashing at every lib reload. I can't figure out if I'm mistaking something or if it's due to a hot-lib-reloader-rs' issue, can you please help me out?

I tested the attached code both on Windows 10 and Arch Linux, with rustc version 1.65.0 and 1.67.0-nightly, and I attempted also using cargo run --features iced/glow with no luck.

commands:
  bin: |
    cargo run
  lib: |
      cargo watch -w lib -x 'build -p lib'

bin

[workspace]
members = ["lib"]

[package]
name = "bin"
version = "0.1.0"
edition = "2021"

[dependencies]
hot-lib-reloader = "0.6.4"
iced = "0.5.2"
lib = { path = "lib" }
#[hot_lib_reloader::hot_module(dylib = "lib")]
mod hot_lib {
    use iced::{Element, Theme};
    pub use lib::*;
    hot_functions_from_file!("lib/src/lib.rs");
}

use hot_lib::*;
use iced::{Element, Sandbox, Settings, Theme};

fn main() -> iced::Result {
    App::run(Settings::default())
}

#[derive(Debug, Default)]
struct App {
    state: State,
}

impl Sandbox for App {
    type Message = Message;

    fn new() -> Self {
        Self::default()
    }

    fn title(&self) -> String {
        title()
    }

    fn update(&mut self, message: Message) {
        update(&mut self.state, message)
    }

    fn view(&self) -> Element<Message> {
        view(&self.state)
    }

    fn theme(&self) -> Theme {
        theme(&self.state)
    }
}

lib

[package]
name = "lib"
version = "0.1.0"
edition = "2021"

[lib]
crate-type = ["rlib", "dylib"]

[dependencies]
iced = "0.5.2"
use iced::widget::{
    button, column, horizontal_rule, horizontal_space, radio, row, text, vertical_space,
};
use iced::{Alignment, Element, Length, Theme};

#[derive(Debug)]
pub struct State {
    text_size: u16,
    theme: Theme,
}

impl Default for State {
    fn default() -> Self {
        Self {
            text_size: 100,
            theme: Theme::default(),
        }
    }
}

#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub enum ThemeType {
    Light,
    Dark,
}

#[derive(Debug, Clone)]
pub enum Message {
    ThemeChanged(ThemeType),
    IncrementTextSizePressed,
    DecrementTextSizePressed,
}

#[no_mangle]
pub fn title() -> String {
    String::from("Hello World - HotIce")
}

#[no_mangle]
pub fn update(state: &mut State, message: Message) {
    let size_change = 10;
    match message {
        Message::ThemeChanged(theme) => {
            state.theme = match theme {
                ThemeType::Light => Theme::Light,
                ThemeType::Dark => Theme::Dark,
            }
        }
        Message::IncrementTextSizePressed => {
            if state.text_size <= 200 - size_change {
                state.text_size += size_change;
            }
        }
        Message::DecrementTextSizePressed => {
            if state.text_size >= size_change {
                state.text_size -= size_change;
            }
        }
    }
}

#[no_mangle]
pub fn view(state: &State) -> Element<Message> {
    let choose_theme = [ThemeType::Light, ThemeType::Dark].into_iter().fold(
        column![text("Choose a theme:")].spacing(10),
        |column, theme| {
            column.push(radio(
                format!("{:?}", theme),
                theme,
                Some(match state.theme {
                    Theme::Light => ThemeType::Light,
                    Theme::Dark => ThemeType::Dark,
                    Theme::Custom { .. } => panic!(),
                }),
                Message::ThemeChanged,
            ))
        },
    );

    column![
        row![choose_theme, horizontal_space(Length::Fill)],
        horizontal_rule(20),
        vertical_space(Length::Fill),
        text("zHello, world!").size(state.text_size),
        vertical_space(Length::Fill),
        row![
            button("Increment Text Size").on_press(Message::IncrementTextSizePressed),
            button("Decrement Text Size").on_press(Message::DecrementTextSizePressed),
        ]
        .spacing(20)
    ]
    .spacing(20)
    .padding(20)
    .align_items(Alignment::Center)
    .into()
}

#[no_mangle]
pub fn theme(state: &State) -> Theme {
    state.theme.clone()
}

Allow custom name/path for hot reload file

I have noticed that the hot reload file is always right next to the original. This does not help when multiple things need to hot reload the same file as the names can conflict.
Consider allowing an alternative name either by an attribute parameter or a static prefix/suffix field on LibReloader we can set.

.join(format!("{lib_name}-hot-{load_counter}"))

`hot_functions_from_file` doesn't find functions created in a macro

I'm trying to hide some of the moving parts by creating the exported, no_mangle'd functions in a macro (exposed from a library) but when I attempt to use it in my reloadable lib, the hot_functions_from_file function doesn't find them.

// mod file1
#[macro_export]
macro_rules! make_exports {
  #[no_mangle]
  pub fn f() -> i32 { 42 }
}

// mod file2
make_exports!();

// main
mod hot_lib {
  hot_functions_from_file("file2/src/lib.rs");
}

fn main() {
  hot_lib::f(); // function not found in `hot_lib`
}

[Feature request] Only reload on request

I'm writing a sort of game engine and I would like to be able to only reload game code on request (say, when a user presses Cmd+R).
Is this currently possible with the library? I couldn't figure it out.

Thanks!

Q: Is copying the library required on *nix?

On *nix you can overwrite a file that's being read by another process.

https://stackoverflow.com/questions/2028874/what-happens-to-an-open-file-handle-on-linux-if-the-pointed-file-gets-moved-or-d

This should mean that the compiler can output a new dynamic library without hitting errors because there's programs still reading (soon to be older versions of) it.
When Casey presents this copy-based workaround, he's just working around the Windows FS guarantees.

Can the copy become Windows-specific?

Segmentation Fault on changing function params

Running on Ubuntu 20.04

When testing out the bevy example, if I add commands to the parameters for player_movement_system then the application seg faults. This happens for any change to the system params I have tested. This may be a fundamental limitation with these techniques though.

segmentation fault

I was trying to use this library for a project that uses syscalls, libc, nix etc (e.g. shared memory, mutex). also, tokio async and unsafe are used across my project. It threw segmentation fault with hot reloading. i am looking some guidance on suitability of using this library.

`Egui`, `Windows`: hot-egui example crashes on reload if updated to eframe version > 0.19.0

The hot-egui example crashes on reload, if eframe is updated to version any version above 0.19.0 (0.20.0, 0.21.0 or 0.22.0), with the following error:

Running `target\debug\hot-egui.exe`
error: process didn't exit successfully: `target\debug\hot-egui.exe` (exit code: 0xc000041d)

Machine specs:

OS: Windows 11 Home (10.0.22621)
CPU: AMD Ryzen 7 5800H
GPU: Geforce RTX 3600

Repro steps:

  • Switch eframe version on hot-egui example from 0.19.0 to any 0.2x.x version
  • Run sample with cargo watch -w lib -x 'build -p lib' and cargo run --features reload
  • Make a change to the render function on lib/src/lib.rs (I usually remove/add the ui.heading)
  • Application freezes and crashes a couple after reload

This is most likely due to a change on eframe, but I'm unsure how to debug this. Any suggestions?

TypeId changes after first reload.

This is an interesting issue which took a while to figure out. While using an ECS which utilizes TypeId to figure out the component types during queries, I ran into the TypeId changing after the first reload. The behavior was so arbitrary and odd though because in one function it changed but another it did not which left me going "HUH" for a day trying to figure it out. So, long bug hunt later, this is what I found and you might want to double check and perhaps document it:

In the 'spawning' function which creates the initial entities, I had something along the lines of:

use crate::ExampleComponent;
#[no_mangle]
pub fn setup() -> Result<()> {
let world = ...blah blah...;
world.spawn((ExampleComponent {counter: 0},));
}

The above produces a stable type id and nothing is wrong there. But later I had a function which was serializing things between reloads:

#[no_mangle]
pub fn before_reload() -> Result<()> {
use crate::ExampleComponent;

let query = world.query::<&ExampleComponent>()?;
... do serialization ...
}

During the initial run before a reload, this all works just fine. But after reload the TypeId is different.

Long story short, move the 'use' function out of the reloaded function and everything is fine and it's stable. After the first reload though, the in function 'use' will be different.

To prevent others from having to track down this goofy behavior this should be documented somewhere perhaps? It certainly had me going in circles with the different behaviors.

Allow specifying `crate = "something::hot_lib_reloader"` on hot_module! for when library is not a direct dependency

Hello,
In order to make it possible to re-export hot_lib_reloader and use hot_module derive, a new parameter crate should be added, to specify the crate name in the context where the proc-macro is called.

I found other example of this in serde and tokio
https://github.com/serde-rs/serde/blob/1fcda0ebdb2cfa050df42fded93ba07c14e4b683/test_suite/tests/test_serde_path.rs#L6

https://docs.rs/tokio/latest/tokio/attr.main.html#rename-package

Changing how Rust source files are found for v0.6

The hot_module attribute macro (via hot_functions_from_file!("path/to/file.rs")) and the define_lib_reloader!(...) macro allow specifying Rust source files from which public #[no_mangle] functions are discovered and for which hot-reloadable proxy functions are created.

So far, both macros expected file relative paths, i.e. paths relative to the file where the macro was defined. So, for example:

├── Cargo.toml
└── src
│   └── main.rs    <- hot_functions_from_file!("../lib/src/lib.rs");
└── lib
    ├── Cargo.toml
    └── src
        └── lib.rs 

Code for this is here: https://github.com/rksm/hot-lib-reloader-rs/blob/cb016a03c68ea2fb2176b683ff466d8bdb8f94e0/macro/src/util.rs

The reasoning behind it was that it might be more intuitive and easier to reason about with larger code bases but there are two problems that requiring a relative path created:

  1. We need Rust nightly to use the proc_macro::Span feature to lookup the path of the file in which the macro was declared
  2. rust-analyzer fails to expand the macro and cannot give code completion

Thinking about it again, requiring that the path of the files to be relative to the project root is not so bad and will solve both of the problems above. But it means that we need to break the interface. For this reason this change requires a version bump to v0.6.

So` instead of the above, the following will be expected:

├── Cargo.toml
└── src
│   └── main.rs    <- hot_functions_from_file!("lib/src/lib.rs");
└── lib
    ├── Cargo.toml
    └── src
        └── lib.rs 

Setting an absolute path to the lib dir.

Unfortunately I have to change the working directory of the application which I was trying this on and found that the reloader blows up because it is assuming the target is relative to current working directory. The initial thought was to do something based on env! and/or related items but I don't believe I can get those passed into the proc-macro attributes properly. (Or I just suck with that bit of meta-programming, very possible.. :) ) So, I was wondering if it would be viable to do something which delays the reloader setup till entry into main such that I could then tell it, at runtime, use xyz path as the root path rather than assuming things are relative.

If I could set it in main, then I can use the env! macro and fiddle the compile time result to point to the correct place, i.e. a rooted path from the manifest directory down to the containing workspace directory where the 'target' folder lives. Or, I'd love a pointer on how to build that path such that the proc macro attribute would accept it correctly. I.e. I need: env!("CARGO_MANIFEST_DIR") + "/../../" to get the correct base path. But, of course that could change if I'm using the lib as a pulled crate rather than a part of the larger project (which is an eventual desire).

Thoughts and suggestions very welcome.

Documentation does not mention that hot reloader only checks for reload if you call a function (at least once?)

While retrofiring my project to use hot-lib-reloader-rs I ran into a very confusing issue, the notification from wait_for_about_to_reload never came back to me. I spent 2 hours comparing my project to the sample, refactoring, debugging, etc.

It turns out that if you don't call into any function in your hot reload API consistently (or at least once?) you don't get any notification.

My use case involves waiting for a reload, and then doing a bunch of work. I was not calling any API before the notification.

Once I created an empty API that I called in my message loop everything started working.

I'm filing this to suggest a documentation change to explain how\when the listening occurs.

Fix CI

The tests that is currently failing is due to the first file change notify event we receive is not actually the write. On other systems that doesn't seem to matter much but the CI machines disk seems to be slow enough that we actually copy and use the old version of the library. That is actually a good catch as it would have happened with growing libraries on other machines as well much likely.

Different Windows issue: "The specified procedure could not be found"

Hey all,

I've been getting the error above any time I try to use the hot reloading in the nannou example (and my own nannou app).
I was able to get the bevy example to run and pick up changes. I've tried to find a significant difference between the two, but nothing I've found has worked.
I've tried copying over the .cargo/ directory, changing the toolchain, renaming files, probably other steps I've forgotten.

This is on Windows 10 Pro 22H2 build 19045.2728. Let me know if any other system information would be helpful.

I was able to repro this with:

  • git clone https://github.com/rksm/hot-lib-reloader-rs.git hlr-repro
  • cd .\hlr-repro\examples\nannou-vector-field\
  • cargo watch -w lib -x "build -p lib" (separate PowerShell terminal)
  • $env:RUST_BACKTRACE="full"; cargo run --features reload --target-dir "target-bin"
Full error $env:RUST_BACKTRACE="full"; cargo run --features reload --target-dir "target-bin" Finished dev [optimized + debuginfo] target(s) in 0.51s Running `target-bin\debug\nannou-vector-field.exe` thread 'main' panicked at 'failed to create hot reload loader: LibraryLoadError(LoadLibraryExW { source: Os { code: 127, kind: Uncategorized, message: "The specified procedure could not be found." } })', src\main.rs:3:1 stack backtrace: 0: 0x7ffb4b789d02 - <std::sys_common::backtrace::_print::DisplayBacktrace as core::fmt::Display>::fmt::hbb6eec21cb8e3d1b 1: 0x7ffb4b7c570b - core::fmt::write::h3f34d6e08b5a11e8 2: 0x7ffb4b77cdfa - <std::io::IoSlice as core::fmt::Debug>::fmt::h1d8b4e60e4c9e548 3: 0x7ffb4b789a4b - std::sys::common::alloc::realloc_fallback::h258dcd65da5985d2 4: 0x7ffb4b78d3f9 - std::panicking::default_hook::h90b70b966050cb60 5: 0x7ffb4b78d07b - std::panicking::default_hook::h90b70b966050cb60 6: 0x7ffb4b78dd30 - std::panicking::rust_panic_with_hook::h1d50ae3462f62f01 7: 0x7ffb4b78dabe - <std::panicking::begin_panic_handler::StrPanicPayload as core::panic::BoxMeUp>::get::h79e1803705c718ce 8: 0x7ffb4b78a9ff - <std::sys_common::backtrace::_print::DisplayBacktrace as core::fmt::Display>::fmt::hbb6eec21cb8e3d1b 9: 0x7ffb4b78d770 - rust_begin_unwind 10: 0x7ffb4b7fb3d5 - core::panicking::panic_fmt::haf2d819891c687b2 11: 0x7ffb4b7fb8f6 - core::result::unwrap_failed::h2ee1097ec86ca0b0 12: 0x7ff73c9f4e3f - enum2$<core::result::Result<hot_lib_reloader::lib_reloader::LibReloader,enum2$<hot_lib_reloader::error::HotReloaderError> > >::expect at /rustc/9eb3afe9ebe9c7d2b84b71002d44f4a0edac95e0\library\core\src\result.rs:1069 13: 0x7ff73c9f4e3f - nannou_vector_field::hot_lib::__lib_loader::closure$0 at C:\Users\DrJKL\Documents\Projects\hlr-repro\examples\nannou-vector-field\src\main.rs:3 14: 0x7ff73c9f4e3f - std::sync::once::impl$2::call_once::closure$0<nannou_vector_field::hot_lib::__lib_loader::closure_env$0> at /rustc/9eb3afe9ebe9c7d2b84b71002d44f4a0edac95e0\library\std\src\sync\once.rs:143 15: 0x7ffb4b7fa905 - std::sys_common::once::queue::Once::call::hede37d70a4218ee8 16: 0x7ff73c9f580c - std::sync::once::Once::call_once at /rustc/9eb3afe9ebe9c7d2b84b71002d44f4a0edac95e0\library\std\src\sync\once.rs:143 17: 0x7ff73c9f580c - nannou_vector_field::hot_lib::__lib_loader at C:\Users\DrJKL\Documents\Projects\hlr-repro\examples\nannou-vector-field\src\main.rs:3 18: 0x7ff73c9f580c - nannou_vector_field::hot_lib::update at C:\Users\DrJKL\Documents\Projects\hlr-repro\examples\nannou-vector-field\src\main.rs:3 19: 0x7ff73c9f0c0c - nannou_vector_field::update at C:\Users\DrJKL\Documents\Projects\hlr-repro\examples\nannou-vector-field\src\main.rs:20 20: 0x7ff73c9e5882 - nannou::app::apply_update<lib::Model,enum2$<nannou::event::Event> > at C:\Users\DrJKL\.cargo\registry\src\github.com-1ecc6299db9ec823\nannou-0.18.1\src\app.rs:1387 21: 0x7ff73c9f27f2 - nannou::app::run_loop::closure$0 at C:\Users\DrJKL\.cargo\registry\src\github.com-1ecc6299db9ec823\nannou-0.18.1\src\app.rs:1104 22: 0x7ff73c9f27f2 - winit::platform_impl::platform::event_loop::impl$2::run_return::closure$0<tuple$<>,nannou::app::run_loop::closure_env$0<lib::Model,enum2$<nannou::event::Event> > > at C:\Users\DrJKL\.cargo\registry\src\github.com-1ecc6299db9ec823\winit-0.25.0\src\platform_impl\windows\event_loop.rs:206 23: 0x7ffb4838212b - alloc::boxed::impl$46::call_mut at /rustc/9eb3afe9ebe9c7d2b84b71002d44f4a0edac95e0\library\alloc\src\boxed.rs:1995 24: 0x7ffb4838212b - winit::platform_impl::platform::event_loop::runner::impl$3::call_event_handler::closure$0 at C:\Users\DrJKL\.cargo\registry\src\github.com-1ecc6299db9ec823\winit-0.25.0\src\platform_impl\windows\event_loop\runner.rs:245 25: 0x7ffb4838212b - core::panic::unwind_safe::impl$23::call_once<tuple$<>,winit::platform_impl::platform::event_loop::runner::impl$3::call_event_handler::closure_env$0<tuple$<> > > at /rustc/9eb3afe9ebe9c7d2b84b71002d44f4a0edac95e0\library\core\src\panic\unwind_safe.rs:271 26: 0x7ffb4841697e - std::panicking::try::do_call at /rustc/9eb3afe9ebe9c7d2b84b71002d44f4a0edac95e0\library\std\src\panicking.rs:483 27: 0x7ffb4841697e - std::panicking::try at /rustc/9eb3afe9ebe9c7d2b84b71002d44f4a0edac95e0\library\std\src\panicking.rs:447 28: 0x7ffb4841697e - std::panic::catch_unwind at /rustc/9eb3afe9ebe9c7d2b84b71002d44f4a0edac95e0\library\std\src\panic.rs:140 29: 0x7ffb4841697e - winit::platform_impl::platform::event_loop::runner::EventLoopRunner<tuple$<> >::catch_unwind at C:\Users\DrJKL\.cargo\registry\src\github.com-1ecc6299db9ec823\winit-0.25.0\src\platform_impl\windows\event_loop\runner.rs:152 30: 0x7ffb4841697e - winit::platform_impl::platform::event_loop::runner::EventLoopRunner<tuple$<> >::call_event_handler<tuple$<> > at C:\Users\DrJKL\.cargo\registry\src\github.com-1ecc6299db9ec823\winit-0.25.0\src\platform_impl\windows\event_loop\runner.rs:239 31: 0x7ffb484160d0 - winit::platform_impl::platform::event_loop::runner::EventLoopRunner<tuple$<> >::move_state_to<tuple$<> > at C:\Users\DrJKL\.cargo\registry\src\github.com-1ecc6299db9ec823\winit-0.25.0\src\platform_impl\windows\event_loop\runner.rs:369 32: 0x7ffb483fa3fa - winit::platform_impl::platform::event_loop::runner::EventLoopRunner<tuple$<> >::main_events_cleared at C:\Users\DrJKL\.cargo\registry\src\github.com-1ecc6299db9ec823\winit-0.25.0\src\platform_impl\windows\event_loop\runner.rs:227 33: 0x7ffb483fa3fa - winit::platform_impl::platform::event_loop::flush_paint_messages<tuple$<> > at C:\Users\DrJKL\.cargo\registry\src\github.com-1ecc6299db9ec823\winit-0.25.0\src\platform_impl\windows\event_loop.rs:703 34: 0x7ffb48385a87 - winit::platform_impl::platform::event_loop::public_window_callback_inner::closure$0<tuple$<> > at C:\Users\DrJKL\.cargo\registry\src\github.com-1ecc6299db9ec823\winit-0.25.0\src\platform_impl\windows\event_loop.rs:891 35: 0x7ffb48415cf2 - core::ops::function::FnOnce::call_once at /rustc/9eb3afe9ebe9c7d2b84b71002d44f4a0edac95e0\library\core\src\ops\function.rs:250 36: 0x7ffb48415cf2 - core::panic::unwind_safe::impl$23::call_once at /rustc/9eb3afe9ebe9c7d2b84b71002d44f4a0edac95e0\library\core\src\panic\unwind_safe.rs:271 37: 0x7ffb48415cf2 - std::panicking::try::do_call at /rustc/9eb3afe9ebe9c7d2b84b71002d44f4a0edac95e0\library\std\src\panicking.rs:483 38: 0x7ffb48415cf2 - std::panicking::try at /rustc/9eb3afe9ebe9c7d2b84b71002d44f4a0edac95e0\library\std\src\panicking.rs:447 39: 0x7ffb48415cf2 - std::panic::catch_unwind at /rustc/9eb3afe9ebe9c7d2b84b71002d44f4a0edac95e0\library\std\src\panic.rs:140 40: 0x7ffb48415cf2 - winit::platform_impl::platform::event_loop::runner::EventLoopRunner<tuple$<> >::catch_unwind<tuple$<>,isize,winit::platform_impl::platform::event_loop::public_window_callback_inner::closure_env$0<tuple$<> > > at C:\Users\DrJKL\.cargo\registry\src\github.com-1ecc6299db9ec823\winit-0.25.0\src\platform_impl\windows\event_loop\runner.rs:152 41: 0x7ffb483fa594 - winit::platform_impl::platform::event_loop::public_window_callback_inner at C:\Users\DrJKL\.cargo\registry\src\github.com-1ecc6299db9ec823\winit-0.25.0\src\platform_impl\windows\event_loop.rs:1980 42: 0x7ffb483fa594 - winit::platform_impl::platform::event_loop::public_window_callback<tuple$<> > at C:\Users\DrJKL\.cargo\registry\src\github.com-1ecc6299db9ec823\winit-0.25.0\src\platform_impl\windows\event_loop.rs:791 43: 0x7ffba279d3b6 - DefSubclassProc 44: 0x7ffba279d26c - DefSubclassProc 45: 0x7ffbb66ae7e8 - CallWindowProcW 46: 0x7ffbb66ae47e - CallWindowProcW 47: 0x7ffb4e8ff0f0 - glPushClientAttrib 48: 0x7ffbb66ae7e8 - CallWindowProcW 49: 0x7ffbb66ae36c - DispatchMessageW 50: 0x7ffbb66c0c23 - SendMessageTimeoutW 51: 0x7ffbb85b0ef4 - KiUserCallbackDispatcher 52: 0x7ffbb5c11704 - NtUserDispatchMessage 53: 0x7ffbb66ae27a - DispatchMessageW 54: 0x7ff73c9e10a4 - winit::platform_impl::platform::event_loop::EventLoop<tuple$<> >::run_return at C:\Users\DrJKL\.cargo\registry\src\github.com-1ecc6299db9ec823\winit-0.25.0\src\platform_impl\windows\event_loop.rs:221 55: 0x7ff73c9e10a4 - winit::platform_impl::platform::event_loop::EventLoop<tuple$<> >::run<tuple$<>,nannou::app::run_loop::closure_env$0<lib::Model,enum2$<nannou::event::Event> > > at C:\Users\DrJKL\.cargo\registry\src\github.com-1ecc6299db9ec823\winit-0.25.0\src\platform_impl\windows\event_loop.rs:191 56: 0x7ff73c9e5762 - winit::event_loop::EventLoop<tuple$<> >::run<tuple$<>,nannou::app::run_loop::closure_env$0<lib::Model,enum2$<nannou::event::Event> > > at C:\Users\DrJKL\.cargo\registry\src\github.com-1ecc6299db9ec823\winit-0.25.0\src\event_loop.rs:154 57: 0x7ff73c9e6071 - nannou::app::Builder<lib::Model,enum2$<nannou::event::Event> >::run<lib::Model,enum2$<nannou::event::Event> > at C:\Users\DrJKL\.cargo\registry\src\github.com-1ecc6299db9ec823\nannou-0.18.1\src\app.rs:494 58: 0x7ff73c9f0c97 - nannou_vector_field::main at C:\Users\DrJKL\Documents\Projects\hlr-repro\examples\nannou-vector-field\src\main.rs:24 59: 0x7ff73c9e3e76 - core::ops::function::FnOnce::call_once at /rustc/9eb3afe9ebe9c7d2b84b71002d44f4a0edac95e0\library\core\src\ops\function.rs:250 60: 0x7ff73c9e3e76 - std::sys_common::backtrace::__rust_begin_short_backtrace<void (*)(),tuple$<> > at /rustc/9eb3afe9ebe9c7d2b84b71002d44f4a0edac95e0\library\std\src\sys_common\backtrace.rs:121 61: 0x7ff73c9f715c - std::rt::lang_start::closure$0<tuple$<> > at /rustc/9eb3afe9ebe9c7d2b84b71002d44f4a0edac95e0\library\std\src\rt.rs:166 62: 0x7ffb4b76db4e - std::rt::lang_start_internal::hca4a39d2276f2843 63: 0x7ff73c9f0ccc - main 64: 0x7ff73ca257f0 - invoke_main at D:\a\_work\1\s\src\vctools\crt\vcstartup\src\startup\exe_common.inl:78 65: 0x7ff73ca257f0 - __scrt_common_main_seh at D:\a\_work\1\s\src\vctools\crt\vcstartup\src\startup\exe_common.inl:288 66: 0x7ffbb6577614 - BaseThreadInitThunk 67: 0x7ffbb85626a1 - RtlUserThreadStart error: process didn't exit successfully: `target-bin\debug\nannou-vector-field.exe` (exit code: 101)p\runner.rs:152 41: 0x7ffb483fa594 - winit::platform_impl::platform::event_loop::public_window_callback_inner at C:\Users\DrJKL\.cargo\registry\src\github.com-1ecc6299db9ec823\winit-0.25.0\src\platform_impl\windows\event_loop.rs:1980 42: 0x7ffb483fa594 - winit::platform_impl::platform::event_loop::public_window_callback > at C:\Users\DrJKL\.cargo\registry\src\github.com-1ecc6299db9ec823\winit-0.25.0\src\platform_impl\windows\event_loop.rs:791 43: 0x7ffba279d3b6 - DefSubclassProc 44: 0x7ffba279d26c - DefSubclassProc 45: 0x7ffbb66ae7e8 - CallWindowProcW 46: 0x7ffbb66ae47e - CallWindowProcW 47: 0x7ffb4e8ff0f0 - glPushClientAttrib 48: 0x7ffbb66ae7e8 - CallWindowProcW 49: 0x7ffbb66ae36c - DispatchMessageW 50: 0x7ffbb66c0c23 - SendMessageTimeoutW 51: 0x7ffbb85b0ef4 - KiUserCallbackDispatcher 52: 0x7ffbb5c11704 - NtUserDispatchMessage 53: 0x7ffbb66ae27a - DispatchMessageW 54: 0x7ff73c9e10a4 - winit::platform_impl::platform::event_loop::EventLoop >::run_return at C:\Users\DrJKL\.cargo\registry\src\github.com-1ecc6299db9ec823\winit-0.25.0\src\platform_impl\windows\event_loop.rs:221 55: 0x7ff73c9e10a4 - winit::platform_impl::platform::event_loop::EventLoop >::run,nannou::app::run_loop::closure_env$0 > > at C:\Users\DrJKL\.cargo\registry\src\github.com-1ecc6299db9ec823\winit-0.25.0\src\platform_impl\windows\event_loop.rs:191 56: 0x7ff73c9e5762 - winit::event_loop::EventLoop >::run,nannou::app::run_loop::closure_env$0 > > at C:\Users\DrJKL\.cargo\registry\src\github.com-1ecc6299db9ec823\winit-0.25.0\src\event_loop.rs:154 57: 0x7ff73c9e6071 - nannou::app::Builder >::run > at C:\Users\DrJKL\.cargo\registry\src\github.com-1ecc6299db9ec823\nannou-0.18.1\src\app.rs:494 58: 0x7ff73c9f0c97 - nannou_vector_field::main at C:\Users\DrJKL\Documents\Projects\hlr-repro\examples\nannou-vector-field\src\main.rs:24 59: 0x7ff73c9e3e76 - core::ops::function::FnOnce::call_once at /rustc/9eb3afe9ebe9c7d2b84b71002d44f4a0edac95e0\library\core\src\ops\function.rs:250 60: 0x7ff73c9e3e76 - std::sys_common::backtrace::__rust_begin_short_backtrace > at /rustc/9eb3afe9ebe9c7d2b84b71002d44f4a0edac95e0\library\std\src\sys_common\backtrace.rs:121 61: 0x7ff73c9f715c - std::rt::lang_start::closure$0 > at /rustc/9eb3afe9ebe9c7d2b84b71002d44f4a0edac95e0\library\std\src\rt.rs:166 62: 0x7ffb4b76db4e - std::rt::lang_start_internal::hca4a39d2276f2843 63: 0x7ff73c9f0ccc - main 64: 0x7ff73ca257f0 - invoke_main at D:\a\_work\1\s\src\vctools\crt\vcstartup\src\startup\exe_common.inl:78 65: 0x7ff73ca257f0 - __scrt_common_main_seh at D:\a\_work\1\s\src\vctools\crt\vcstartup\src\startup\exe_common.inl:288 66: 0x7ffbb6577614 - BaseThreadInitThunk 67: 0x7ffbb85626a1 - RtlUserThreadStart error: process didn't exit successfully: `target-bin\debug\nannou-vector-field.exe` (exit code: 101)

My own approach to hot-reloading.

By coincidence I made my own hot-reloading test just a few weeks ago when I read fasterthanli's article
However I find that, my setup. Is a tad bit less of a hassle to set-up.
I also watch for file changes & use libloading.
However I use:
cargo-watch -x "build -Z unstable-options --out-dir ./plugins"
Then simply watch the directory for Event::Write events.

I only need to specify the plugin directory, which I find less of a hassle to write compared to

hot_lib_reloader::define_lib_reloader! {
    unsafe MyLibLoader {
        lib_name: "lib",
        source_files: ["path/to/lib.rs"]
    }
}

For each library.

However you need to check the function signature in the source file, and must therefore know the source location.
Except the source location embedded in the library if it's a debug build so you may extract it from there.
I'm sure there's a library that can extract it easily, but I wouldn't the name on top of my head. (maybe symbolic-debuginfo?)

If you're interested, here's my main method for my reload test. ( Probably adopted from fasterthanli, I can't remember )

use std::{error::Error, path::Path};

// use hotwatch::{Event, Hotwatch}; // Non blocking
use hotwatch::blocking::Hotwatch;   //     Blocking
use libloading::{Library, Symbol};

static PLUGINS_LIBRARY_PATH: &str = "."; // Relative to the process' current working directory
fn main() -> Result<(), Box<dyn Error>> {
    let mut hotwatch = Hotwatch::new().expect("hotwatch failed to initialize!");

    hotwatch.watch(PLUGINS_LIBRARY_PATH, |event| {
        if let hotwatch::Event::Write(path) = event {
            if path.extension() == Some("so".as_ref()) {load_and_print(&path).expect("Error loading so file.");}
        }

        hotwatch::blocking::Flow::Continue
    })?;
    hotwatch.run();
    Ok(())
}

fn load_and_print(path: &Path) -> Result<(), libloading::Error> {
    unsafe {
        let lib = Library::new(path)?;
        let greet: Symbol<unsafe extern "C" fn(name: String)> = lib.get(b"greet")?;
        greet("reloading".to_string());
    }

    Ok(())
}

Support for reloading impl methods

Currently impl methods are not supported, only plain functions. impl methods can be declared as no mangle as well so there is no reason to not support them. This will require some thinking about invocation: method(&self, arg) will work but self.method(arg) not without some work...

RWLock is a bit too heavy. Considering performance issues, can Atomic be used?

result from rust-lock-bench

==== num_reader: 20, num_writer: 0 ====
mutex => mean: 45.71, std_dev: 5.35
rwlock => mean: 134.71, std_dev: 17.67
prwlock => mean: 134.71, std_dev: 17.67
seq_cst => mean: 1.00, std_dev: 0.00
relaxed => mean: 1.00, std_dev: 0.00
unsync => mean: 1.00, std_dev: 0.00

==== num_reader: 19, num_writer: 1 ====
mutex => mean: 50.29, std_dev: 7.63
rwlock => mean: 113.71, std_dev: 19.61
prwlock => mean: 113.71, std_dev: 19.61
seq_cst => mean: 4.57, std_dev: 6.80
relaxed => mean: 3.00, std_dev: 2.65
unsync => mean: 1.57, std_dev: 1.51

Windows sometimes doesn't work

All the examples provided work except the bevy one,

LINK : fatal error LNK1104: can't open file '.\hot-lib-reloader-rs\examples\bevy\target\debug\deps\systems.dll

no_mangle attribute in production systems

If I understood correctly, the HotModule will parse and expose as hot-reloadable all public no_mangle functions, but I was wondering if marking systems functions as #[no_mangle] may impact production performance, since AFAIK Rust compiler won't be able to fully otimize those functions.

It's possible somehow to only mark #[no_mangle] on hot-reload builds?

Including functions from multiple files is a bit tedious

The hot_functions_from_file! macro is very helpful, but it only works on single files (which most non-trivial projects grow out of quite quickly). If a user wants to break up their project into multiple files for manageability, their options are:

  • Redefine the hot-reloaded library's whole API either in a single file for hot_functions_from_file! to pick up or directly in the hot_module
  • Multiple calls to hot_functions_from_file! for every file in the project

Both of these things are rather clumsy and would be unnecessary if hot_functions_from_file! didn't only work on single files. I took a look at the package source to see if there's any technical limitation preventing the macro from working on folders too but as far as I can tell, the only thing is that if a user uses the ignore_no_mangle option there is the possibility of duplicate function names in different files.

Proposal

Provide a new hot_functions_from_folder! or hot_functions_from_crate! macro that recursively pulls public no-mangled function definitions from a provided folder or crate path. Unlike hot_functions_from_file! it will not take an ignore_no_mangle argument, so that the function names/symbols are known to be unique.

I don't mind implementing it myself if you're good with this but that would have to be over the weekend.

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.