aabtop / reify Goto Github PK
View Code? Open in Web Editor NEWReify lets you embed a customized TypeScript runtime within a C++ application.
License: MIT License
Reify lets you embed a customized TypeScript runtime within a C++ application.
License: MIT License
Startup delay for a simple TS file is not terrible right now (~100-200ms), but it's not nothing either.
This delay was introduced when moving from JS to TS. I haven't profiled it yet so it's possible that it is caused by the TypeScript compiler running on the input file, however I suspect it is more likely the fact that we're now having V8 compile the TypeScript compiler on startup each time. The TSC is ~8mb unminified and ~2mb minified.
A great example source of how to setup a V8 snapshot is given by V8's mksnapshot tool: https://github.com/v8/v8/blob/master/src/snapshot/mksnapshot.cc . This tool may work for us, but I believe it is designed to be used internally by the v8 codebase. There is a fair bit of complexity as well around how to handle cross-compilation for different CPU architectures, which is handled already in the V8 build system, but this project doesn't have direct access to that logic.
One nice compromise is to cache a snapshot on startup to a user's cache directory, so that it's guaranteed to run on the device.
Currently, as Hypo makes CGAL calls to process geometry, there is no error checking. If the input is invalid and it causes an error, the program will crash, and if we're luckily running in debug there may be an assertion line number to look at.
We should make it so that errors can be properly reported and propagated back to the user.
In the future, we could potentially associate TypeScript callstacks with the errors, but this means we'd have to associate TypeScript callstacks with each Reify node. But that might actually be a really nice feature.
Some potential errors we may wish to report:
A situation arose where the code:
build(): h.Region3 {
return
h.Difference3({
a: h.Difference3({
a: Box3(this.width, this.length, this.height),
b: Box3(this.width - (this.wall_thickness * 2), this.length - (this.wall_thickness * 2), this.height - (this.wall_thickness * 2)),
}),
b: h.Transform3({ source: Box3(2, 2, 2), transform: h.Translate3([0, 0, 3]) })
});
}
Resulted in a segmentation fault. What was happening was that the first return
statement on its own line was being interpreted as a statement on its own, and somehow getting past the type checker as it is clearly not returning a h.Region3 here.
Perhaps there are some TypeScript compiler settings that would enable more strict checking of types here?
Attached is the full file that seg faults on hypo bad_house.ts Main house
.
Making this change will reduce the verbosity of the code, since we would no longer need to redundantly type the struct name twice (e.g. "Mesh3Union" in the example from the subject).
It also makes it easier to determine the "outputs" of processing a node, for example an IDE could autocomplete after typing "Mesh3Union(...)." to show all outputs of the Mesh3Union operation (in many cases it may be just a mesh).
The down side to this is that our objects become a bit chunkier in that they now also have to hold references to their potential outputs. It also complicates things because you can no longer query the ability to determine how to produce a Mesh3 by simply querying all functions that output Mesh3s, since now you may also get a Mesh3 via a property of an object.
We assume certain characters, when the shift key is pressed, will result in specific shift key characters, but this is only true for US keyboards. I should translate virtual keys from the OS to corresponding platform-abstract virtual keys, and not do any shift key processing.
Also, I'm not converting all keys right now, I'm not even sure if the underscore character can be typed.
Currently, the "enum" based approach, which is native to Haskell and Rust, has a few downsides:
It would be nice to know how we are spending our time in various parts of the pipeline. With hypo for example, it would be nice to know how much time we spend in:
It would be nice if we could monitor these results over time for a collection of sample user scripts in a sample app like hypo.
Currently we have code like this which includes frequent references to "reify.":
let jeep_wheel = reify.Cylinder(0.35, 0.15);
let my_jeep = reify.Mesh3UnionAsMesh3(reify.Mesh3Union({
meshes: [
reify.TranslatedMesh3(jeep_wheel, [1.0, 0.0, -0.5]),
reify.TranslatedMesh3(jeep_wheel, [1.0, 0.0, -0.5]),
reify.TranslatedMesh3(jeep_wheel, [1.0, 0.0, 0.5]),
reify.TranslatedMesh3(jeep_wheel, [-1.0, 0.0, 0.5]),
reify.TranslatedMesh3(jeep_wheel, [-1.0, 0.0, -0.5]),
]
}));
It would read nicer (and be easier to explain the system to a beginner) if we didn't have to prefix all Reify functions with "reify.".
Pros:
Cons:
In order to implement this, we would want to run the TypeScript compiler on the Reify interface ".ts" files during the build phase. We would have it produce a ".d.ts" file as well as a ".js" file. We would pass the ".d.ts" file into the embedded/wrapped TypeScript Compiler by appending it to the default library source. We would pass the ".js" file to be pre-loaded by the Reify-runtime before loading any modules, which also gives us the opportunity to create a snapshot of the implementation as well.
This includes:
--make_workspace_dir
.reify.h
.Currently we're able to call functions that take no parameters, but we don't support calling Reify functions that require parameters. This would be useful, for example, to enable functions that take a time parameter to express animations.
In order for this to work, we'll need a system for converting from C++ Immut Ref types to V8 bindings types. We have logic for going the other direction already to support function return values.
We will need to parse and understand function type signatures that contain parameters, then convert these parameters to V8 bindings types, and then setup V8 bindings functionality to invoke the function calls with the appropriate parameters.
In order to maintain efficiency, we will want to keep a registry of C++ Immut Ref objects known to be associated with V8 objects, so that we can avoid re-creating these. For example, if a V8 function returns an object, and we then pass that as a parameter to another V8 function, it should not have to convert it to a V8 object because it already exists as a V8 object.
In other words, make a module that can be re-used across projects where:
Input: An IDT definition, probably provided as Haskell.
Output: A CMake library project that, when built, would provide a public API to apps like the following:
shared_ptr<MyReifyType> GetMyReifyTypeFromScript() {
ReifyCompilerEnvironment compile_env;
ReifyCompiledModule module = compile_env.Compile("my_script.ts");
auto my_entrypoint = module.GetFunction<Function<shared_ptr<MyReifyType> (int32_t)>>("main");
if (my_entrypoint) {
return my_entrypoint->Call(42);
}
return shared_ptr<MyReifyType>();
So in the example above, MyReifyType is defined in the IDT, and has both a generated V8 wrapper object representation as well as a generated pure C++ immutable object implementation (which is intended to be passed around as a shared_ptr).
In order to actually implement this "CMake project as-an-output" idea, Nix can be leveraged. We create a Nix derivation which takes the IDT as an input and produces the CMake project as an output. The process will include the generation of all build-time resources, such as configuring the embedded TypeScript compiler, snapshot management, and etc... This entire process can be wrapped in a docker container that offers a clear and concise input/output setup, so that the tool can be used on Windows and other machines where Nix is not installed.
This would enable us to communicate with functions as objects back and forth between C++ and TypeScript.
There's a few complicating factors here though, the biggest one being that this does not translate very well to different backends. In other words, so far all of our types represent abstract data that can be serialized to almost any format. As soon as functions are introduced, a type that references a function could no longer be serialized to JSON for example because it really depends a lot on the runtime. A function object is really really tied to the runtime environment, so it would be a relatively "non-pure" data object in that sense. Of course, if a certain serialization mechanism does not support functions, it can always raise an error, but it's still less elegant.
That said, it would be a hugely powerful object to be able to express. It would effectively enable monads in Reify in that by returning a function to host C++, it can act as a continuation to perform more computation after I/O has been performed. It can also enable lazy evaluation.
This would certainly require support for function parameters ( #12 ) as a prerequisite.
The Reify interface definition should include support for associating comments with the defined types. This way, code generators can access comment information and include that in the generated files so that the information is available in many different forms.
This would, for example, enable TypeScript and C++ code completion tools to automatically display help text when typing out a function call.
Currently we are using the most accurate type of Kernel, Exact_predicates_exact_constructions_kernel
, however it is also the slowest.
Most third party code examples using CGAL use Exact_predicates_inexact_constructions_kernel
, so it is likely noticeably faster, however when I try switching there's a few compile errors to work though. It's probably worth working through them though.
Currently, within the playground container, no linting is taking place.
As a command line utility function, Hypo leaves a lot to be desired. It's a tool for creating visual elements, but it does not contain any support for actually visualizing its results. It would be essential to have a 2D/3D viewing environment to enable a fast workflow for developing something. This is something that OpenSCAD has done very well with in that it sets up an editor and a viewer side-by-side for fast iteration.
Whenever a JavaScript IDT object is created, it should result in the creation of a pure C++ shared_ptr object as well. So you will have the JavaScript wrapper which is wrapping an object that knows nothing about JavaScript. The wrapped object ought to be immutable so that it can be passed off immediately to other systems.
It's not clear to me at the moment if it would be better to just duplicate the object data within the wrapper, so that JavaScript can access the data (if it needs to) without crossing through the bindings layer, potentially speeding things up, and definitely simplifying things.
An implication here is that when linking together objects that reference other objects, the links will definitely need to be duplicated by the wrapper (so that JavaScript can traverse the link, and keep referenced objects alive for the GC), but also so the JS-independent wrapped object to be able to express the link.
This is kind of a no-brainer issue, it's just a matter of time/priority.
The TypeScript compiler supports various different module settings, one of which is 'es2015'. By supporting this, we would enable scripts to be defined in separate files which is good for two reasons:
Currently I've grayed out the option as I'm not sure what the issue is.
When enabled and executed it produces the following message:
terminate called after throwing an instance of 'CGAL::Assertion_exception'
what(): CGAL ERROR: assertion violation!
Expr: ss_circle.has_on(sp)
File: /nix/store/8v1d7px814h07lklxddj50rbw9yvpi68-cgal-5.0-x86_64-unknown-linux-musl/include/CGAL/Nef_3/polygon_mesh_to_nef_3.h
Line: 255
Aborted
But it's not clear what's wrong. Could be related to #17.
This is pretty much essential in reducing verbosity to acceptable levels, especially when more complicated types are introduced and we need to enable more obscure features.
For example, a circle could have a default radius of 1
and a default center of [0, 0]
, which would make circles instantiable without any parameters.
The hard part of this is deciding how and where to describe the default values. Ideally we would describe them in the same place we define the type, but that is in Haskell, and the implication here is that we'd need to make the codegen system work with values as well as types (and map between them), which is a lot of added complexity. In addition we'll need to write codegen from Haskell values to each different language... It would be nice though if TypeScript could introspect the values resulting from defaults.
Alternatively we could just implement defaults as optional values, and if a value doesn't exist, then it's up to the runtime to decide what that means and e.g. assign defaults. The downside here is that different runtimes may interpret this differently, plus TypeScript wouldn't be able to know what a value gets defaulted to, and there is little support for documenting the defaults.
I think we should make this work somehow with defaults encoded into Haskell, even if it's more difficult. It will enable much easier to generate automatic documentation.
Unfortunately it appears that the use of any
is possible and can enable TS code to return an any
type for any type.
Since Reify's backend code assumes that the types are correct, this can result in seg faults when things are not as they are expected to be.
Either we should add proper checks on the C++ side to just double check that everything is as expected (we should probably do this anyway), or we can search around for stricter methods (beyond the --strict
TS compiler option which we already enable) to ensure absolute type safety. So far all I can find is that TSLint offers this with their no-any rule, but even then it's not clear if that will apply to the core ES libraries that have any
embedded in them.
Creation of spheres should be trivial in hypo, we need to add support for this.
While module support is provided by the existing system, users can still not load additional external modules, only internal modules compiled into the executable.
We need to design a model for how users can reference other modules, and implement it.
Perhaps something like letting the user specify a workspace root directory, and then all referenced modules will be relative to that?
Input would be a 2d/3d region as well as a cutting plane, output would be the input region minus the half-volume specified by the plane.
These can increase safety and usability of the library and user TS code.
They could raise an exception, which we can watch for and print diagnostics about, including a stack trace. This could also potentially be used as a makeshift breakpoint/debugging tool.
There's a lot more code in the header file right now than there needs to be, it could be moved into the corresponding cc file.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.