Runtime Compiled C++ for Rapid AI Development [pdf]
gameaipro.com
gameaipro.com
Let's hope the future of closures in all languages (C++ through to JS / lua) is a rich one. The ability to synthesize code at runtime is powerful, but it would be much more powerful if it were better-supported.
The hoops these guys have to go through to get past console data-protection bits is insane.
It uses shared libraries (DLLs) to do so, but manages the creation of these for you so you don't have to set it up. Only the required changes are re-compiled and linked into the shared library, which improves turn around times over approachs such as UE4's Hot Reload which recompiles the entire game lib.
Cool if you can pull it of, but seems very house-of-cards. All you need is one bad reference floating around and boom.
I kinda wonder what this would look like in Rust. You could track all the lifetimes appropriately and make sure nothing got a handle into code that could be dynamically unloaded.
You'd keep all the native performance gains but without being nearly so brittle.
I use RCC++ daily as part of my development routine, and although you can introduce problems which mean you need to relaunch the program, the fact that you save having to do so most of the time is a big win for iteration.
For instance your crash protection catches execution failures, but what about data failures? If you've got a write-past on an object nothing is going to be keeping you from destroying the rest of the world(unless all allocations in a hot-reload scenario are constrained to separate pages that you can protect?).
Seems like a lot if things you're trying to avoid by catching crashes are built-in to Rust.
So there may be benefits to using Rust, but the issues you've posed are not ones I've found were my primary concerns when working on code which needs fast development iteration.
- Rust as-is assumes program code/static memory is always valid/has the 'static lifetime. This assumption is implicit in the sense of function pointers, trait object vtables, static variables etc all having no lifetime bounds on something else than 'static, which means unloading their backing memory would cause unsafety.
- Thus, in order to make safe code unloading safe there would have to be a new, optional compile mode in which the compiler would treat "static" things like function pointers, trait objects, statics etc as non-'static, eg by giving them the new concrete lifetime "'crate". So if you have a "static FOO: T;" in a unloadable crate, you could only get a "&'crate T" to it, and you could only coerce to trait objects with a "+ 'crate" lifetime bound.
- 'crate would be similar to 'static in that it represents memory valid for the lifetime of the crate, but unlike 'static would not imply being always valid, and as such the borrow checker would prevent a bunch of operations for it like subtyping with any other lifetime, or usage with API's that want 'static bounds like thread spawning.
- Because 'crate is distinct from 'static, mixing unloadable and loadable code would be safe since the regular lifetime checking would ensure correct interaction between both. It means unloadable crates need to be explicitly written as such though.
- There are some major complications with generics and other compiler-generated glue code like vtables that are not fully hashed out yet: The issue there is that machine code corresponding to an upstream dependency gets compiled into the binary of the current crate, which means you would have code that typechecked with the assumptions of living in one binary be generated in another. Solutions here include banning generics and trait objects to types from extern crates, re-checking generics at instantiation location similar to C++ templates, or adding a new feature for instantiating generics outside the current crate, similar to "extern template" in C++.
Also C++ has unique_ptr that also tracks lifetimes, I don't think Rust would really give you anything you don't already have and I think you are overestimating the fragility. I've done hot reloading of .dll and it isn't really all that fragile. If you have the c runtime as a dynamic .dll the heap won't be tied to the .dll either.
For example, this Rust program:
fn main() {
let r: &i32;
{
let b = Box::new(i32);
r = &*b;
}
// r is dangling here, this would be bad:
println!("{}", r);
}
Is caught at compile time. Rust keeps track of the lifetime of both b and r, and knows that r will be dangling.The C++ version:
#include<iostream>
#include<memory>
using namespace std:
int main() {
int *r;
{
uniq_ptr<int> b(new int(5));
r = &*b;
}
// r is dangling here
cout << r << endl
}
This compiles with no warnings under -Wall -Wextra with no warnings, and happily does whatever you ask with r, even though it's dangling.Yes, this specific example is a bit contrived, but such is the way of examples. My point is just that C++'s smart pointers are a great thing, but that doesn't mean they do everything that Rust's do.
The article covers a number of areas where scripting languages fall down in practice as a solution for fast iteration in many area of game development. Please see section 15.2.1.
No tool is perfect. Don't knock solutions to problems you don't have.
Aw come on. Don't make Lisp programmers look even worse. It's embarrassing.
Also, this breaks the HN guideline against calling names in arguments.
I fear it doesn't.
It wasn't your best attempt at moderating. Just my direct perception.