Giter Site home page Giter Site logo

aabtop / reify Goto Github PK

View Code? Open in Web Editor NEW
6.0 6.0 0.0 2.28 MB

Reify lets you embed a customized TypeScript runtime within a C++ application.

License: MIT License

Dockerfile 1.10% C++ 76.17% Haskell 8.28% TypeScript 6.33% JavaScript 0.19% Batchfile 0.72% Shell 0.34% HTML 0.05% PowerShell 0.03% Starlark 6.54% GLSL 0.20% C 0.05%

reify's People

Contributors

aabtop avatar dependabot[bot] avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar

reify's Issues

Use V8 snapshots to decrease startup delay of TSC.

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.

Add support for error checking during geometry construction.

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:

  • Polygon soups that are not oriented correctly and cannot be converted to Nef_polyhedron_3s.
  • Extrusions that result in invalid meshes that cannot be converted to Nef_polyhedron_3s.

Blank return statements do not raise an exception from the TS compiler.

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.

Enable syntax like "Mesh3Union(...).asMesh3" instead of "Mesh3UnionAsMesh3(Mesh3Union(...))".

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.

Keyboard key code platform abstraction is very low-quality.

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.

Switch to variant/union based tagged union for IDT, instead of enum based.

Currently, the "enum" based approach, which is native to Haskell and Rust, has a few downsides:

  1. It doesn't translate nicely to a variant/union based environment like C++, where an extra datastructure is unnecessarily introduced. In fact, this is also true of enum languages, where you often have "struct Foo; struct Bar;" and "enum MyEnum = MyEnumFoo Foo | MyEnumBar Bar", which is redundant.
  2. There is no concept of naming enum constructor parameters, so it encourages nameless fields.

Setup a performance monitoring framework

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:

  1. TypeScript Compiler compilation in V8 (of which snapshots #1 could bring to 0, more or less).
  2. Compilation of the user script (this depends, of course, on the user script).
  3. Execution of the user script results (this depends on the app, so e.g. for hypo it would be the CGAL calls).

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.

Reduce verbosity of script files by putting reify.* declarations in to the default namespace.

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:

  • Less verbose code.
  • No need to explain the concept of modules/namespaces to beginners.

Cons:

  • It introduces the possibility for a naming conflict between a Reify type and a third party library.

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.

Add support for calling Reify functions with parameters.

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.

Package the "Embedded TypeScript In-A-Box" aspect of the project as a standalone component.

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.

Support the "function" type in Reify.

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.

Enable Reify types to have comments associated with them.

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.

Switch CGAL to use `Exact_predicates_inexact_constructions_kernel`

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.

Design an interactive workflow for Hypo.

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.

Refactor V8 bindings so that IDT objects create an embedded shared_ptr C++ component.

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.

Enable module scripts via support for es2015 modules.

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:

  1. Script code can be reused.
  2. Reused code can be cached and also v8-snapshotted, improving startup time.

CatmullClark subdivision fails at runtime.

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.

Enable Reify struct members to have default values.

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.

No compiler errors raised when `any` type is used.

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.

Design/implement model for references to external user-defined modules.

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?

Add a 2d/3d cut command.

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.

Add support for assert() statements.

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.

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.