Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ jobs:
fail-fast: false
max-parallel: 1
matrix:
os: [ubuntu-20.04]
os: [ubuntu-24.04]

steps:
- uses: actions/checkout@v3
Expand Down
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,10 @@ Address to source map is hardcoded in `tools/generate.py` script and is always '

Also, symlink to adaptagrams should be in `examples` directory to provide libavoid sources for debugger.

## Lessons learned

- WebIDL is a powerful concept, but implementation in emscripten toolset is not very actively developed and if certain feature is missing such as support of callbacks or you encounter a bug, you should be ready to implement or to fix it by yourself. We started with WebIDL(own fork with implementation of callbacks, support of prefixed enums and more) and then switched to embind. Embind supports more features, so probability you need to modify it is lower, and it is more performant.

## History

This project was started as part of a research project at [Technische Universität Wien](https://www.tuwien.at) and is further developed by its author and contributors.
Expand Down
25 changes: 21 additions & 4 deletions dist/index-node.mjs

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions dist/index-node.mjs.map

Large diffs are not rendered by default.

25 changes: 21 additions & 4 deletions dist/index.js

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions dist/index.js.map

Large diffs are not rendered by default.

Binary file modified dist/libavoid.wasm
Binary file not shown.
137 changes: 137 additions & 0 deletions embind/bindings.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
#include <libavoid/libavoid.h>

#include <emscripten/bind.h>

using namespace emscripten;


void setConnRefCallback(Avoid::ConnRef& conn, emscripten::val jsCallback) {
auto thunk = [&conn, jsCallback](uintptr_t userData) {
jsCallback(conn);
};

conn.setCallbackFunction(thunk, reinterpret_cast<uintptr_t>(&conn));
}


EMSCRIPTEN_BINDINGS(my_module) {
class_<Avoid::Router>("Router")
.constructor<unsigned long>()
.function("processTransaction", &Avoid::Router::processTransaction)
.function("printInfo", &Avoid::Router::printInfo)
.function("deleteConnector", &Avoid::Router::deleteConnector, allow_raw_pointers())
.function("moveShape_poly", select_overload<void(Avoid::ShapeRef*, const Avoid::Polygon&, const bool)>(&Avoid::Router::moveShape), allow_raw_pointers())
.function("moveShape_delta", select_overload<void(Avoid::ShapeRef*, double, double)>(&Avoid::Router::moveShape), allow_raw_pointers())
.function("deleteShape", &Avoid::Router::deleteShape, allow_raw_pointers())
.function("moveJunction_point", select_overload<void(Avoid::JunctionRef*, const Avoid::Point&)>(&Avoid::Router::moveJunction), allow_raw_pointers())
.function("moveJunction_delta", select_overload<void(Avoid::JunctionRef*, double, double)>(&Avoid::Router::moveJunction), allow_raw_pointers())
.function("setRoutingParameter", &Avoid::Router::setRoutingParameter)
.function("setRoutingOption", &Avoid::Router::setRoutingOption);

emscripten::enum_<Avoid::RouterFlag>("RouterFlag")
.value("PolyLineRouting", Avoid::RouterFlag::PolyLineRouting)
.value("OrthogonalRouting", Avoid::RouterFlag::OrthogonalRouting);

emscripten::enum_<Avoid::RoutingOption>("RoutingOption")
.value("nudgeOrthogonalSegmentsConnectedToShapes", Avoid::RoutingOption::nudgeOrthogonalSegmentsConnectedToShapes)
.value("improveHyperedgeRoutesMovingJunctions", Avoid::RoutingOption::improveHyperedgeRoutesMovingJunctions)
.value("penaliseOrthogonalSharedPathsAtConnEnds", Avoid::RoutingOption::penaliseOrthogonalSharedPathsAtConnEnds)
.value("nudgeOrthogonalTouchingColinearSegments", Avoid::RoutingOption::nudgeOrthogonalTouchingColinearSegments)
.value("performUnifyingNudgingPreprocessingStep", Avoid::RoutingOption::performUnifyingNudgingPreprocessingStep)
.value("improveHyperedgeRoutesMovingAddingAndDeletingJunctions", Avoid::RoutingOption::improveHyperedgeRoutesMovingAddingAndDeletingJunctions)
.value("nudgeSharedPathsWithCommonEndPoint", Avoid::RoutingOption::nudgeSharedPathsWithCommonEndPoint);


emscripten::enum_<Avoid::RoutingParameter>("RoutingParameter")
.value("segmentPenalty", Avoid::RoutingParameter::segmentPenalty)
.value("anglePenalty", Avoid::RoutingParameter::anglePenalty)
.value("crossingPenalty", Avoid::RoutingParameter::crossingPenalty)
.value("clusterCrossingPenalty", Avoid::RoutingParameter::clusterCrossingPenalty)
.value("fixedSharedPathPenalty", Avoid::RoutingParameter::fixedSharedPathPenalty)
.value("portDirectionPenalty", Avoid::RoutingParameter::portDirectionPenalty)
.value("shapeBufferDistance", Avoid::RoutingParameter::shapeBufferDistance)
.value("idealNudgingDistance", Avoid::RoutingParameter::idealNudgingDistance)
.value("reverseDirectionPenalty", Avoid::RoutingParameter::reverseDirectionPenalty);

class_<Avoid::Point>("Point")
.constructor<>()
.constructor<double, double>()
.function("equal", &Avoid::Point::operator==)
.property("x", &Avoid::Point::x)
.property("y", &Avoid::Point::y)
.property("id", &Avoid::Point::id)
.property("vn", &Avoid::Point::vn);

register_vector<Avoid::Point>("vector<Avoid::Point>");

class_<Avoid::PolygonInterface>("PolygonInterface")
.function("clear", &Avoid::PolygonInterface::clear)
.function("empty", &Avoid::PolygonInterface::empty)
.function("size", &Avoid::PolygonInterface::size)
.function("id", &Avoid::PolygonInterface::id)
.function("at", &Avoid::PolygonInterface::at, allow_raw_pointers()) // returns const Point&
.function("boundingRectPolygon", &Avoid::PolygonInterface::boundingRectPolygon)
.function("offsetBoundingBox", &Avoid::PolygonInterface::offsetBoundingBox)
.function("offsetPolygon", &Avoid::PolygonInterface::offsetPolygon);

class_<Avoid::Polygon, base<Avoid::PolygonInterface>>("Polygon")
.constructor<>()
.constructor<long>()
.property("ps", &Avoid::Polygon::ps)
.function("setPoint", &Avoid::Polygon::setPoint)
.function("size", &Avoid::Polygon::size);

class_<Avoid::Rectangle, base<Avoid::Polygon>>("Rectangle")
.constructor<const Avoid::Point&, double, double>()
.constructor<const Avoid::Point&, const Avoid::Point&>();

class_<Avoid::Obstacle>("Obstacle")
.function("id", &Avoid::Obstacle::id)
.function("polygon", &Avoid::Obstacle::polygon)
.function("position", &Avoid::Obstacle::position)
.function("setNewPoly", &Avoid::Obstacle::setNewPoly);

class_<Avoid::ShapeRef, emscripten::base<Avoid::Obstacle>>("ShapeRef")
.constructor<Avoid::Router*, Avoid::Polygon&>()
.constructor<Avoid::Router*, Avoid::Polygon&, long>()
.function("polygon", &Avoid::ShapeRef::polygon)
.function("position", &Avoid::ShapeRef::position)
.function("setNewPoly", &Avoid::ShapeRef::setNewPoly);

enum_<Avoid::ConnType>("ConnType")
.value("ConnType_None", Avoid::ConnType_None)
.value("ConnType_PolyLine", Avoid::ConnType_PolyLine)
.value("ConnType_Orthogonal", Avoid::ConnType_Orthogonal);

class_<Avoid::Checkpoint>("Checkpoint")
.constructor<const Avoid::Point&>();

class_<Avoid::ConnRef>("ConnRef")
.constructor<Avoid::Router*, const Avoid::ConnEnd&, const Avoid::ConnEnd&>()
.constructor<Avoid::Router*, const Avoid::ConnEnd&, const Avoid::ConnEnd&, unsigned long>()
.function("id", &Avoid::ConnRef::id)
.function("setCallback", &setConnRefCallback)
.function("setSourceEndpoint", &Avoid::ConnRef::setSourceEndpoint)
.function("setDestEndpoint", &Avoid::ConnRef::setDestEndpoint)
.function("routingType", &Avoid::ConnRef::routingType)
.function("setRoutingType", &Avoid::ConnRef::setRoutingType)
.function("displayRoute", &Avoid::ConnRef::displayRoute, allow_raw_pointers())
.function("setHateCrossings", &Avoid::ConnRef::setHateCrossings)
.function("doesHateCrossings", &Avoid::ConnRef::doesHateCrossings);

class_<Avoid::ConnEnd>("ConnEnd")
.constructor<const Avoid::Point&>()
.constructor<Avoid::ShapeRef*, unsigned long>()
.class_function("createConnEndFromJunctionRef", &Avoid::ConnEnd::createConnEndFromJunctionRef, allow_raw_pointers());

class_<Avoid::ShapeConnectionPin>("ShapeConnectionPin")
.constructor<Avoid::ShapeRef*, unsigned long, double, double, bool, double, Avoid::ConnDirFlags>()
.constructor<Avoid::ShapeRef*, unsigned long, double, double, double, Avoid::ConnDirFlags>()
.constructor<Avoid::JunctionRef*, unsigned long, Avoid::ConnDirFlags>()
.function("setConnectionCost", &Avoid::ShapeConnectionPin::setConnectionCost)
.function("position", &Avoid::ShapeConnectionPin::position)
.function("directions", &Avoid::ShapeConnectionPin::directions)
.function("setExclusive", &Avoid::ShapeConnectionPin::setExclusive)
.function("isExclusive", &Avoid::ShapeConnectionPin::isExclusive)
.function("updatePosition", &Avoid::ShapeConnectionPin::updatePosition);
}
106 changes: 106 additions & 0 deletions examples/benchmark2.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
// debug web version
// import { AvoidLib } from './debug-dist/index.mjs';
// production web version
import { AvoidLib } from '../dist/index.js';

async function main() {
await AvoidLib.load();
const Avoid = AvoidLib.getInstance();

const router = new Avoid.Router(Avoid.RouterFlag.OrthogonalRouting.value);

// Set routing parameters
// const routingParameters = [
// [Avoid.RoutingParameter.shapeBufferDistance.value, 30],
// [Avoid.RoutingParameter.idealNudgingDistance.value, 10]
// ];
// routingParameters.forEach(([param, value]) => router.setRoutingParameter(param, value));

// // Set routing options
// const routingOptions = [
// [Avoid.RoutingOption.nudgeOrthogonalTouchingColinearSegments.value, false],
// [Avoid.RoutingOption.performUnifyingNudgingPreprocessingStep.value, true],
// [Avoid.RoutingOption.nudgeSharedPathsWithCommonEndPoint.value, true],
// [Avoid.RoutingOption.nudgeOrthogonalSegmentsConnectedToShapes.value, true]
// ];
// routingOptions.forEach(([option, value]) => router.setRoutingOption(option, value));

// Rectangle dimensions
const w = 120, h = 120;

// Define all rectangles' top-left points
const rectangles = [
{ x: 0, y: 680 },
{ x: 320, y: 1055 },
{ x: 320, y: 1225.5 },
// 3
{ x: 320, y: 1225 },
{ x: 320, y: 1565 },
// 5
{ x: 320, y: 1735 },
{ x: 320, y: 1905 },
{ x: 320, y: 2075 },
// 8
{ x: 640, y: 1605 },
{ x: 320, y: 2245 },
// 10
{ x: 640, y: 25 },
{ x: 320, y: 25 },
// 12
{ x: 0, y: 1900 },
{ x: 960, y: 2560 },
// 14
{ x: 640, y: 2522.5 },
// you can continue with more...
];

// Create shapes
const shapeRefs = rectangles.map(({ x, y }) => {
const topLeft = new Avoid.Point(x, y);
const bottomRight = new Avoid.Point(x + w, y + h);
const rect = new Avoid.Rectangle(topLeft, bottomRight);
return new Avoid.ShapeRef(router, rect);
});


function connCallback(connRef) {
console.log(`Connector ${connRef.id()} needs rerouting!`);
const route = connRef.displayRoute();
console.log('New path: ', route, route.size());
console.log('----------');
for (let i = 0; i < route.size(); i++) {
console.log(`(${route.ps.get(i).x}, ${route.ps.get(i).y})`);
}
console.log('----------');
}

function connectShapes(shape1, shape2) {
const shapeConnectionPinSrc1 = new Avoid.ShapeConnectionPin(shape1, 1, 0.5, 0.5, true, 0, 15);
shapeConnectionPinSrc1.setExclusive(false);
const connRefSrcConnEnd = new Avoid.ConnEnd(shape1, 1);
const shapeConnectionPinDest1 = new Avoid.ShapeConnectionPin(shape2, 1, 0.5, 0.5, true, 0, 15);
shapeConnectionPinDest1.setExclusive(false);
const connRefDestConnEnd = new Avoid.ConnEnd(shape2, 1);
const connRef = new Avoid.ConnRef(router, connRefSrcConnEnd, connRefDestConnEnd);
connRef.setCallback(connCallback);
return connRef;
}

const connections = [];
const numConnections = 2;
for (let i = 0; i < shapeRefs.length; i++) {
// create two way connections between shapes
for (let j = 0; j < shapeRefs.length; j++) {
// Create multiple connections between shape[i] and shape[j]
for (let k = 0; k < numConnections; k++) {
console.log(`Creating edge between Shape${i} -> Shape${j} Connection No: ${k}`);
connections.push(connectShapes(shapeRefs[i], shapeRefs[j]));
}
}
}
console.time('Transaction')
router.processTransaction();
console.timeEnd('Transaction')
}

main()
Loading
Loading