Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
79a4b55
feat(mp-api): add support for using exchange and branching libraries …
tassiluca Jul 2, 2025
05b2f11
feat(js-mp-api): complete JS API full support (#130)
tassiluca Oct 2, 2025
53d4a6d
test: field neighborValues always contains also the self value
tassiluca Oct 7, 2025
7aa8bba
test: use new submodules name
tassiluca Oct 9, 2025
ebc1494
build: remove unused optics-macro dependency
tassiluca Oct 9, 2025
19932c2
feat(scafi-mp): add support to native api
tassiluca Oct 9, 2025
311b296
refactor: reuse uint8array utils
tassiluca Oct 10, 2025
40d6fe5
test: add native branch restriction test
tassiluca Oct 10, 2025
cb3b208
refactor: aggregate common native code in ad hoc utilities
tassiluca Oct 12, 2025
1b5bb82
refactor: remove unused protobuf plugin and improve namings
tassiluca Oct 12, 2025
611688d
refactor: reduce by hand native types relying on sn-bindgen plugin
tassiluca Oct 21, 2025
a2a5858
refactor: reduce iso boilerplate
tassiluca Oct 21, 2025
60cd1f5
refactor: use all types from generated sn-bindgen
tassiluca Oct 21, 2025
928208b
refactor: use consistent names for branch api parameters
tassiluca Oct 22, 2025
ae5d567
feat: add mp support for`share`
tassiluca Oct 23, 2025
9c30340
fix: do not expose to native scala-allocated object since they may be…
tassiluca Oct 25, 2025
295b360
refactor: no more need for public device id
tassiluca Oct 25, 2025
bb8d43b
refactor: simplify async engine only on mp bindings
tassiluca Oct 25, 2025
60c71c9
refactor: remove unused code
tassiluca Oct 27, 2025
c36aa10
refactor: adapt to new engine constructor
tassiluca Oct 28, 2025
4dbc8db
test: be stricter in testing treating all warnings as errors
tassiluca Oct 28, 2025
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
15 changes: 14 additions & 1 deletion .github/workflows/build-and-deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,13 @@ jobs:
strategy:
fail-fast: false
matrix:
os: [windows-2025, macos-14, ubuntu-24.04]
include: # see sn-bindgen requirements at https://sn-bindgen.indoorvivants.com/quickstart/index.html
- os: windows-2025
llvm-version: 19
- os: macos-14
llvm-version: 17
- os: ubuntu-24.04
llvm-version: 17
runs-on: ${{ matrix.os }}
concurrency:
group: build-${{ github.workflow }}-${{ matrix.os }}-${{ github.event.number || github.ref }}
Expand All @@ -18,6 +24,13 @@ jobs:
uses: actions/[email protected]
with:
fetch-depth: 0
- name: Setup native toolchain
uses: aminya/setup-cpp@v1
with:
compiler: llvm-${{ matrix.llvm-version }}
make: true
clang: true
gcc: true
- uses: nicolasfara/[email protected]
with:
should-run-codecov: ${{ runner.os == 'Linux' }}
Expand Down
2 changes: 1 addition & 1 deletion .scalafmt.conf
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ docstrings.wrap = yes

# Project
project.git = true
project.excludeFilters = ["build/", "build.sbt"]
project.excludeFilters = ["build/", "build.sbt", "project/"]

# Indent
indent.main = 2
Expand Down
83 changes: 76 additions & 7 deletions build.sbt
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
import scala.scalanative.build.*
import sbtcrossproject.CrossProject
import org.scalajs.linker.interface.OutputPatterns
import scala.scalanative.build.{ BuildTarget, GC, LTO, Mode }
import sbtcrossproject.CrossProject
import BuildUtils.{ MacOS, Windows, nativeLibExtension, os }
import bindgen.interface.Binding

val scala3Version = "3.7.3"

ThisBuild / scalaVersion := scala3Version
ThisBuild / name := "scafi3"
ThisBuild / organization := "it.unibo.scafi"
ThisBuild / homepage := Some(url("https://github.com/scafi/scafi3"))
ThisBuild / licenses := List("Apache-2.0" -> url("https://www.apache.org/licenses/LICENSE-2.0"))
Expand Down Expand Up @@ -60,6 +63,9 @@ val commonScalacOptions = Seq(
ThisBuild / semanticdbEnabled := true
ThisBuild / semanticdbVersion := scalafixSemanticdb.revision

val ExclusiveTestTag = Tags.Tag("exclusive-test")
Global / concurrentRestrictions += Tags.exclusive(ExclusiveTestTag)

lazy val commonSettings = Seq(
libraryDependencies ++= Seq(
"org.typelevel" %%% "cats-core" % "2.13.0",
Expand All @@ -73,12 +79,38 @@ lazy val commonSettings = Seq(
)

lazy val commonNativeSettings = Seq(
nativeConfig ~= {
_.withLTO(LTO.full)
nativeConfig := {
// macOS requires an additional linking option to correctly set the runtime path of the dynamic
// library using the `-rpath` option. For more information, see https://stackoverflow.com/a/66284977.
val additionalLinkingOptions = if (os == MacOS)
Seq(s"-Wl,-install_name,'@rpath/lib${(ThisBuild / name).value}.dylib'")
else Nil
nativeConfig.value
.withLTO(LTO.full)
.withMode(Mode.releaseSize)
.withGC(GC.immix)
.withBuildTarget(BuildTarget.libraryDynamic)
.withBaseName((ThisBuild / name).value)
.withLinkingOptions(nativeConfig.value.linkingOptions ++ additionalLinkingOptions)
.withCheck(true)
.withCheckFeatures(true)
.withCheckFatalWarnings(true)
},
Compile / nativeLink := { // TODO: extract in a nicer dsl into project with additional linking options for macos
val out = (Compile / nativeLink).value
val linkerOutputDir = target.value / "nativeLink"
IO.createDirectory(linkerOutputDir)
val libName = (ThisBuild / name).value
val prefix = if (os == Windows) "" else "lib"
val targetFile = linkerOutputDir / s"$prefix$libName.$nativeLibExtension"
IO.move(out, targetFile)
if (os == Windows) {
val libFile = out.getParentFile / s"$libName.lib"
IO.move(libFile, linkerOutputDir / s"$libName.lib")
}
targetFile
},
scalacOptions ++= Seq("-Wconf:msg=unused import&src=.*[\\\\/]src_managed[\\\\/].*:silent"),
coverageEnabled := false,
)

Expand All @@ -87,7 +119,11 @@ lazy val commonJsSettings = Seq(
_.withModuleKind(ModuleKind.ESModule)
.withOutputPatterns(OutputPatterns.fromJSFile("%s.mjs"))
.withOptimizer(true)
.withMinify(true)
.withCheckIR(true)
},
Compile / fastLinkJS / scalaJSLinkerOutputDirectory := target.value / "fastLinkJS",
Compile / fullLinkJS / scalaJSLinkerOutputDirectory := target.value / "fullLinkJS",
coverageEnabled := false,
)

Expand Down Expand Up @@ -117,6 +153,39 @@ lazy val `scafi3-distributed` = crossProject(JSPlatform, JVMPlatform, NativePlat
),
)

lazy val `scafi3-mp-api` = crossProject(JSPlatform, JVMPlatform, NativePlatform)
.crossType(CrossType.Full)
.in(file("scafi3-mp-api"))
.dependsOn(`scafi3-core` % "compile->compile;test->test", `scafi3-distributed`)
.enablePlugins(BindgenPlugin)
.nativeSettings(
commonNativeSettings,
bindgenBindings += Binding(
header = (Compile / resourceDirectory).value / "include" / "scafi3.h",
packageName = "it.unibo.scafi.nativebindings"
)
)
.jsSettings(commonJsSettings)
.settings(
commonSettings,
name := "scafi3-mp-api",
libraryDependencies ++= Seq(
"org.scala-js" %% "scalajs-stubs" % "1.1.0" % "provided",
),
)

lazy val `scafi3-integration` = project
.in(file("scafi3-integration"))
.dependsOn(`scafi3-distributed`.jvm % "compile->compile;test->test")
.settings(
commonSettings,
publish / skip := true,
Test / test := (Test / test)
.dependsOn(`scafi3-mp-api`.js / Compile / fullLinkJS, `scafi3-mp-api`.native / Compile / nativeLink)
.tag(ExclusiveTestTag)
.value,
)

val alchemistVersion = "42.3.15"
lazy val `alchemist-incarnation-scafi3` = project
.settings(commonSettings)
Expand Down Expand Up @@ -150,10 +219,10 @@ lazy val example = project
lazy val root = project
.in(file("."))
.enablePlugins(ScalaUnidocPlugin)
.aggregate(`alchemist-incarnation-scafi3`)
.aggregate(crossProjects(`scafi3-core`, `scafi3-distributed`).map(_.project)*)
.aggregate(`alchemist-incarnation-scafi3`, `scafi3-integration`)
.aggregate(crossProjects(`scafi3-core`, `scafi3-distributed`, `scafi3-mp-api`).map(_.project)*)
.settings(
name := "scafi3",
name := (ThisBuild / name).value,
publish / skip := true,
publishArtifact := false,
)
Expand Down
21 changes: 21 additions & 0 deletions project/BuildUtils.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
object BuildUtils {

sealed trait OS
case object Windows extends OS
case object MacOS extends OS
case object Linux extends OS

def os: OS = {
val osName = System.getProperty("os.name").toLowerCase
if (osName.contains("win")) Windows
else if (osName.contains("mac")) MacOS
else if (osName.contains("nux")) Linux
else throw new Exception(s"Unsupported OS: $osName")
}

def nativeLibExtension: String = os match {
case Windows => "dll"
case MacOS => "dylib"
case Linux => "so"
}
}
1 change: 1 addition & 0 deletions project/plugins.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.5.5")
addSbtPlugin("com.github.sbt" % "sbt-unidoc" % "0.6.0")
addSbtPlugin("org.scoverage" % "sbt-scoverage" % "2.4.0")
addSbtPlugin("ch.epfl.scala" % "sbt-scalafix" % "0.14.4")
addSbtPlugin("com.indoorvivants" % "bindgen-sbt-plugin" % "0.2.4")
Original file line number Diff line number Diff line change
Expand Up @@ -105,4 +105,26 @@ object Grids:
program = program,
networkFactory = networkFactory,
).grid(sizeX, sizeY)

/**
* Creates a Von Neumann grid. Each device is identified by an integer id from `0` to `rows * cols - 1`, assigned in
* row-major order (i.e., left to right, top to bottom).
* @param rows
* the number of rows of the grid
* @param cols
* the number of columns of the grid
* @param f
* a function that takes a device id and its neighbors' ids and returns a result
* @return
* a sequence of results, one for each device in the grid
*/
def vonNeumannGrid[Result](rows: Int, cols: Int)(f: (Int, Set[Int]) => Result): Seq[Result] =
val areConnected = (a: Int, b: Int) =>
val (ax, ay) = (a / cols, a % cols)
val (bx, by) = (b / cols, b % cols)
Position(ax, ay, 0.0).distanceTo(Position(bx, by, 0.0)) <= SAFE_VON_NEUMANN_RADIUS
for
i <- 0 until rows * cols
neighbors = (0 until rows * cols).filter(areConnected(i, _)).filter(_ != i).toSet
yield f(i, neighbors)
end Grids
79 changes: 79 additions & 0 deletions scafi3-integration/src/test/resources/c/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
########################################################################################################################
# Makefile for building the C integration test with the ScaFi library.
########################################################################################################################

ifeq ($(OS),Windows_NT)
EXE = main.exe
CC = gcc
SCAFI3_LIB = scafi3.dll scafi3.lib
LIBLINK = scafi3.lib
DOWNLOAD_CMD = powershell -Command "Invoke-WebRequest -Uri $(1) -OutFile $(2)"
else ifeq ($(shell uname -s),Darwin)
EXE = main
# On MacOS, usually gcc is an alias to clang, so we try to find a real GCC installation
CC := $(shell \
if gcc --version 2>/dev/null | grep -q "^gcc"; then \
echo gcc; \
else \
find /opt/homebrew/bin /usr/local/bin -name 'gcc-[0-9]*' 2>/dev/null | sort -V | tail -1 || \
echo "NOT_FOUND"; \
fi)
ifeq ($(CC),NOT_FOUND)
$(error GCC not found. Install: brew install gcc)
endif
SCAFI3_LIB = libscafi3.dylib
LIBLINK = -L. -Wl,-rpath,. -lscafi3
DOWNLOAD_CMD = curl -fsSL $(1) -o $(2)
else
EXE = main
CC = gcc
SCAFI3_LIB = libscafi3.so
LIBLINK = -L. -Wl,-rpath,. -lscafi3
DOWNLOAD_CMD = curl -fsSL $(1) -o $(2)
endif

# Protobuf dependencies
PROTOBUF_C_VERSION = 1.5.2
PROTOBUF_C_BASE = https://raw.githubusercontent.com/protobuf-c/protobuf-c/v$(PROTOBUF_C_VERSION)/protobuf-c
VENDOR_DIR = vendor
PROTOBUF_C_DIR = $(VENDOR_DIR)/protobuf-c
PROTOBUF_C_SRC = $(PROTOBUF_C_DIR)/protobuf-c.c
PROTOBUF_C_HDR = $(PROTOBUF_C_DIR)/protobuf-c.h

# Detect protobuf files
PROTO_C_FILES := $(wildcard *.pb-c.c)
PROTO_H_FILES := $(wildcard *.pb-c.h)

CFLAGS = -std=gnu11 -Wall -Wextra -Werror

# Configure protobuf settings if .pb-c files are found
ifneq ($(PROTO_C_FILES),) # Protobuf found - add includes and libs
SOURCES = main.c $(PROTO_C_FILES) $(PROTOBUF_C_SRC)
PROTOBUF_INCLUDES = -I$(VENDOR_DIR)
PROTOBUF_DEPS = $(PROTOBUF_C_SRC) $(PROTOBUF_C_HDR)
else # No protobuf
SOURCES = main.c
PROTOBUF_INCLUDES =
PROTOBUF_DEPS =
endif

all: $(EXE)

$(PROTOBUF_C_DIR):
mkdir -p $(PROTOBUF_C_DIR)

$(PROTOBUF_C_SRC): | $(PROTOBUF_C_DIR)
@echo "Downloading protobuf-c.c..."
$(call DOWNLOAD_CMD,$(PROTOBUF_C_BASE)/protobuf-c.c,$(PROTOBUF_C_SRC))

$(PROTOBUF_C_HDR): | $(VENDOR_DIR)
@echo "Downloading protobuf-c.h..."
$(call DOWNLOAD_CMD,$(PROTOBUF_C_BASE)/protobuf-c.h,$(PROTOBUF_C_HDR))

$(EXE): $(SOURCES) $(PROTO_H_FILES) $(SCAFI3_LIB) $(PROTOBUF_DEPS)
$(CC) $(CFLAGS) $(PROTOBUF_INCLUDES) -I. -I$(VENDOR_DIR) $(SOURCES) $(LIBLINK) -o $(EXE)

.PHONY: all clean

clean:
rm -rf $(EXE) $(VENDOR_DIR)
38 changes: 38 additions & 0 deletions scafi3-integration/src/test/resources/c/main.template.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/*
* Warning: this file is a template used for testing purposes and it is not intended to be executed directly.
* It will be processed by the test suite to replace placeholders (i.e., `{{ ... }}`) with actual values and
* inject the aggregate program to be tested.
*/
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#ifdef _WIN32
#include <windows.h>
#define sleep(x) Sleep((x) * 1000)
#else
#include <unistd.h>
#endif
#include "scafi3.h"
#include "utils.h"
#include "int_codable.c"

#define ITERATIONS 10

const void* last_round_result = NULL;

const void* aggregate_program(const AggregateLibrary* lang);

bool on_result(const void* result) {
static int round = 0;
last_round_result = result;
sleep(1);
return ++round < ITERATIONS;
}

int main(void) {
Neighborhood neighbors = Neighborhood_empty();
{{ neighbors }}
engine(device({{ deviceId }}), {{ port }}, neighbors, aggregate_program, on_result);
printf("%s", field_to_str((const Field*) last_round_result));
return 0;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
const void* aggregate_program(const AggregateLibrary* lang) {
return lang->exchange(lang->Field.of(lang->local_id()), fn(ReturnSending*, (const Field* f), {
return return_sending(f, lang->Field.of(lang->local_id()));
}));
}
Loading
Loading