Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
85 commits
Select commit Hold shift + click to select a range
e7e15f7
Handle full fixed buffers and build without exceptions (2.1.3)
dallison Oct 9, 2026
3d5eca6
Generate messages in dependency order, avoid presence accessor collis…
dallison Oct 9, 2026
2e758c3
Fork toolbelt's PayloadBuffer into phaser/runtime
dallison Oct 19, 2026
586635f
PayloadBuffer: segregated free lists for small blocks
dallison Oct 19, 2026
f67108b
Only sync sub-messages that can hold cached ROS values
dallison Oct 19, 2026
4554e65
PayloadBuffer: inline allocate/free fast paths
dallison Oct 19, 2026
6e4a23f
Key runtime metadata entries by a hash of the type name
dallison Oct 19, 2026
0b0fb06
Prime field location caches for mutable messages
dallison Oct 19, 2026
28be7bc
Cut per-element costs for nested messages
dallison Oct 19, 2026
b8e8343
Skip SyncToPayload recursion into types with no sync work
dallison Oct 19, 2026
84bfc50
Pass runtime handles to message constructors by const reference
dallison Oct 19, 2026
2ead191
Initialize message runtime handles directly in constructors
dallison Oct 19, 2026
2689828
Speed up ROS serialization of fixed-size messages
dallison Oct 19, 2026
356c73a
Speed up ROS deserialization
dallison Oct 19, 2026
1dc1e2c
Don't sync the payload before ROS serialization
dallison Oct 19, 2026
cc3023f
Skip the runtime control block for leaf messages
dallison Oct 19, 2026
635440b
Prime field locations for readonly messages with compiled metadata
dallison Oct 19, 2026
cd64fd1
Cheaper MessageVectorField::Add() and in-place VectorPush
dallison Oct 19, 2026
a7ad4c6
Release repeated message elements without clearing them
dallison Oct 19, 2026
5cb86f1
Write ROS time values through element handles and skip repeated syncs
dallison Oct 19, 2026
ee71586
Skip offset validation when writing a buffer this process owns
dallison Oct 19, 2026
cf28eed
Store repeated messages inline in chunks that never move
dallison Oct 19, 2026
0d9e003
Inline the message handle constructor and drop unused vtables
dallison Oct 19, 2026
6177cf9
Drop the per-field lookup cache and hoist setter loads
dallison Oct 19, 2026
232ffa4
Make element and nested message handles borrow the message
dallison Oct 19, 2026
b2ba357
Install the metadata of every reachable type when a message is made
dallison Oct 20, 2026
4243b06
Store nested messages inline in the ROS frontend
dallison Oct 20, 2026
6fed6bc
Count a generated message's header once in its size
dallison Oct 20, 2026
4923d0d
Use declared ROS widths in the protobuf/ROS converters
dallison Oct 20, 2026
9fcafa7
Write ROS vector elements straight from the payload
dallison Oct 20, 2026
8a82110
Parse ROS vector elements straight into the payload
dallison Oct 20, 2026
76aa2d7
Inline ROS string reads and fresh string stores in the parse path
dallison Oct 20, 2026
115c3d3
Walk vector chunks with a pointer when parsing fixed-size elements
dallison Oct 20, 2026
d5aa52d
Parse nested messages and vectors straight into the payload from ROS
dallison Oct 20, 2026
10e95c7
Clear a main message by resetting its buffer
dallison Oct 20, 2026
cbc94bd
Clear only the handles of nested messages when resetting a buffer
dallison Oct 20, 2026
41e78a2
Append message vector elements from the header pointer again
dallison Oct 20, 2026
bd9d53e
Read and write presence bits a byte at a time
dallison Oct 20, 2026
77cac3b
Keep mutability in each handle and let Add() borrow the runtime
dallison Oct 20, 2026
b9415a8
Let a readonly message reach its nested messages through Mutable()
dallison Oct 20, 2026
fb484ba
Hold active_message in a phaser::ActiveMessage
dallison Oct 20, 2026
8e820a8
Make the handle of a nested message when it is first used
dallison Oct 20, 2026
dd46d92
Look up a message type's metadata where it was last found first
dallison Oct 20, 2026
82f133c
Write fixed-size ROS messages without a check per field
dallison Oct 20, 2026
b7549ae
Store ROS scalars whole on little-endian hosts
dallison Oct 20, 2026
14e1e3b
Make small vector elements parsed from ROS in a local copy
dallison Oct 20, 2026
620c600
Find a message type's metadata inline when it was where last found
dallison Oct 20, 2026
f12a748
Store the head of a staged vector element as one word
dallison Oct 20, 2026
3038172
Read runs of ROS scalars with one bounds check
dallison Oct 20, 2026
4f7f016
Skip the vector setup of empty ROS sequences when parsing
dallison Oct 20, 2026
28b88e5
Reserve and add to message vectors with fewer reloads
dallison Oct 20, 2026
55ec267
Store set ROS times straight into the payload of a mutable message
dallison Oct 20, 2026
e068a4c
Add vectors of scalar-only messages in bulk through a writer
dallison Oct 21, 2026
05b4ca2
Put the locations of generated fields in their types
dallison Oct 21, 2026
3b38765
Bind nested message handles without checking the runtime in mutable m…
dallison Oct 21, 2026
ab095ee
Check the metadata of a readonly message once
dallison Oct 21, 2026
15edadf
Test reading a finalized buffer with a newer schema
dallison Oct 21, 2026
4b03d44
Detect payload buffers from their first byte
dallison Oct 21, 2026
bcd2555
Hold the message runtime by pointer, owning only dynamic ones
dallison Oct 21, 2026
1d54a54
Assign a protobuf-frontend message as a whole
dallison Oct 21, 2026
1d50aef
Shrink the message runtime to 16 bytes
dallison Oct 21, 2026
16d6362
Fix data loss when Realloc moves a block into the free block below
dallison Oct 21, 2026
9e91125
Insert into vectors in place
dallison Oct 21, 2026
1cb3f70
Erase from vectors in place and swap them without copying
dallison Oct 21, 2026
d15cf42
Copy strings safely from the buffer they are copied into
dallison Oct 21, 2026
74920c8
Version 3.0.0
dallison Oct 21, 2026
f3e94ca
Copy new fixed buffers from a prebuilt image
dallison Oct 22, 2026
e6df05e
Store new strings without a call to allocate or copy them
dallison Oct 22, 2026
cfe7fad
Parse a root ROS message straight into its payload
dallison Oct 22, 2026
d345b4a
Skip syncing a message field whose handle holds no new value
dallison Oct 22, 2026
4f3a59e
Read strings of a mutable message without a call
dallison Oct 22, 2026
7bee35b
Build a new fixed message's runtime in its handle
dallison Oct 22, 2026
7168b43
Set a new string without saving registers or finding its source again
dallison Oct 22, 2026
07b66b9
Write nested messages to ROS with a cursor held in registers
dallison Oct 22, 2026
9c841b9
Merge the ASIL fixed-buffer safety work into Phaser 3.
dallison Oct 22, 2026
c8c3b91
Make fixed-buffer creation and Add() as fast as before the safety checks
dallison Oct 22, 2026
94f8b93
Fail allocations in a full buffer without testing the flag on each one
dallison Oct 23, 2026
3c86b20
Share the failure path of Clear() for messages owning one block
dallison Oct 23, 2026
785c228
Grow a ProtoBuffer enough for each write
dallison Oct 23, 2026
e89eb50
Add a build option to serialize without checking for room
dallison Oct 23, 2026
cec656b
Set short strings without a call to copy them
dallison Oct 23, 2026
ad01840
Drop a free block check that only the inline allocator made
dallison Oct 23, 2026
db04382
Check a runtime control block's bounds with one compare
dallison Oct 23, 2026
132a7ac
Sync an unchanged ROS time field with only a test
dallison Oct 23, 2026
67e8613
Clear strings of a mutable message without checking their offsets
dallison Oct 23, 2026
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
270 changes: 270 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,270 @@
# CMakeLists.txt for the phaser project
#
# Builds the phaser runtime library, the protoc-gen-phaser protoc plugin, and
# provides phaser_add_library() to generate a C++ library from .proto files.
#
# A parent project that already provides Abseil (absl::*), cpp_toolbelt
# (toolbelt) and protobuf (protobuf::libprotoc, protobuf::protoc) targets is
# used as is. Missing dependencies are fetched with FetchContent.
#
# When cross-compiling, the plugin must run on the build host, so it is not
# built. Build it for the host separately and pass its path in
# PHASER_PLUGIN_EXECUTABLE.

cmake_minimum_required(VERSION 3.15)
project(phaser LANGUAGES CXX)

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)

if(CMAKE_CROSSCOMPILING)
set(PHASER_BUILD_COMPILER_DEFAULT OFF)
else()
set(PHASER_BUILD_COMPILER_DEFAULT ON)
endif()
option(PHASER_BUILD_COMPILER "Build the protoc-gen-phaser plugin"
${PHASER_BUILD_COMPILER_DEFAULT})
set(PHASER_PLUGIN_EXECUTABLE "" CACHE FILEPATH
"Host protoc-gen-phaser used when the plugin is not built here")
option(PHASER_TRUST_SERIALIZED_SIZE
"Serialize messages without checking for room, trusting the caller"
OFF)

include(FetchContent)

if(NOT TARGET absl::base)
set(ABSL_PROPAGATE_CXX_STD ON)
# protobuf's install rules export targets that link Abseil.
set(ABSL_ENABLE_INSTALL ON)
FetchContent_Declare(
abseil
GIT_REPOSITORY https://github.com/abseil/abseil-cpp.git
GIT_TAG 20250814.1
)
FetchContent_MakeAvailable(abseil)
endif()

if(NOT TARGET toolbelt)
# Matches the cpp_toolbelt git_override in MODULE.bazel.
FetchContent_Declare(
cpp_toolbelt
GIT_REPOSITORY https://github.com/dallison/cpp_toolbelt.git
GIT_TAG 86b45cdd3da46a97a4476be2f2a61909677284a2
)
FetchContent_MakeAvailable(cpp_toolbelt)
endif()

if(PHASER_BUILD_COMPILER AND NOT TARGET protobuf::libprotoc)
set(protobuf_BUILD_TESTS OFF CACHE INTERNAL "")
FetchContent_Declare(
protobuf
GIT_REPOSITORY https://github.com/protocolbuffers/protobuf.git
GIT_TAG v34.1
)
FetchContent_MakeAvailable(protobuf)
endif()

# --- Runtime library ---
add_library(phaser_runtime STATIC
phaser/runtime/md5.cc
phaser/runtime/message.cc
phaser/runtime/phaser_bank.cc
)
add_library(phaser::runtime ALIAS phaser_runtime)
target_include_directories(phaser_runtime PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
target_link_libraries(phaser_runtime PUBLIC
absl::flat_hash_map
absl::status
absl::statusor
absl::strings
absl::str_format
absl::span
toolbelt
)
if(PHASER_TRUST_SERIALIZED_SIZE)
target_compile_definitions(phaser_runtime PUBLIC
PHASER_TRUST_SERIALIZED_SIZE)
endif()

# --- protoc plugin ---
if(PHASER_BUILD_COMPILER)
if(DEFINED protobuf_SOURCE_DIR)
set(PHASER_PROTOBUF_IMPORT_DIR "${protobuf_SOURCE_DIR}/src" CACHE PATH
"Directory containing google/protobuf/*.proto")
else()
set(PHASER_PROTOBUF_IMPORT_DIR "" CACHE PATH
"Directory containing google/protobuf/*.proto")
endif()
if(NOT PHASER_PROTOBUF_IMPORT_DIR)
message(FATAL_ERROR
"Set PHASER_PROTOBUF_IMPORT_DIR to the directory containing "
"google/protobuf/descriptor.proto")
endif()

set(PHASER_OPTIONS_SRC "${CMAKE_CURRENT_BINARY_DIR}/phaser/options.pb.cc")
set(PHASER_OPTIONS_HDR "${CMAKE_CURRENT_BINARY_DIR}/phaser/options.pb.h")
add_custom_command(
OUTPUT ${PHASER_OPTIONS_SRC} ${PHASER_OPTIONS_HDR}
COMMAND $<TARGET_FILE:protobuf::protoc>
ARGS --cpp_out=${CMAKE_CURRENT_BINARY_DIR}
--proto_path=${CMAKE_CURRENT_SOURCE_DIR}
--proto_path=${PHASER_PROTOBUF_IMPORT_DIR}
${CMAKE_CURRENT_SOURCE_DIR}/phaser/options.proto
DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/phaser/options.proto protobuf::protoc
COMMENT "Generating C++ code from phaser/options.proto"
)

add_executable(protoc-gen-phaser
phaser/compiler/enum_gen.cc
phaser/compiler/gen.cc
phaser/compiler/main.cc
phaser/compiler/message_gen.cc
${PHASER_OPTIONS_SRC}
)
target_include_directories(protoc-gen-phaser PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}
${CMAKE_CURRENT_BINARY_DIR}
)
target_link_libraries(protoc-gen-phaser PRIVATE
absl::flat_hash_map
absl::flat_hash_set
absl::status
absl::statusor
absl::strings
absl::str_format
protobuf::libprotoc
protobuf::libprotobuf
)
endif()

# phaser_add_library(<name>
# PROTOS <proto>...
# IMPORT_DIRS <dir>...
# [IMPORTS <proto>...]
# [PACKAGE <package>]
# [ADD_NAMESPACE <namespace>]
# [FRONTEND protobuf|ros]
# [PROTOC <protoc>]
# [PLUGIN <protoc-gen-phaser>]
# [COMPILE_OPTIONS <option>...])
#
# Generates phaser C++ code for PROTOS and builds it into the static library
# <name>. Proto files are named by their import path and found in
# IMPORT_DIRS. IMPORTS lists the imported .proto files that need phaser code
# too, such as google/protobuf/any.proto. descriptor.proto and
# phaser/options.proto need none.
#
# As with the Bazel phaser_library rule, the generated files live in
# <package>/<name>/<proto path>.phaser.{h,cc} and each file in PROTOS also gets
# a short header, so "proto/foo.proto" is included as "proto/foo.phaser.h".
# PACKAGE defaults to the directory of the first file in PROTOS.
#
# PROTOC defaults to the protobuf::protoc target. PLUGIN defaults to the
# protoc-gen-phaser target, or PHASER_PLUGIN_EXECUTABLE when that is set.
function(phaser_add_library name)
cmake_parse_arguments(ARG ""
"PACKAGE;ADD_NAMESPACE;FRONTEND;PROTOC;PLUGIN"
"PROTOS;IMPORTS;IMPORT_DIRS;COMPILE_OPTIONS"
${ARGN})
if(NOT ARG_PROTOS)
message(FATAL_ERROR "phaser_add_library(${name}) needs PROTOS")
endif()

if(NOT DEFINED ARG_PACKAGE)
list(GET ARG_PROTOS 0 first_proto)
get_filename_component(ARG_PACKAGE "${first_proto}" DIRECTORY)
endif()
if(NOT ARG_FRONTEND)
set(ARG_FRONTEND protobuf)
endif()

set(tool_depends)
if(NOT ARG_PROTOC)
if(NOT TARGET protobuf::protoc)
message(FATAL_ERROR "phaser_add_library(${name}) needs PROTOC")
endif()
set(ARG_PROTOC $<TARGET_FILE:protobuf::protoc>)
list(APPEND tool_depends protobuf::protoc)
endif()
if(NOT ARG_PLUGIN)
if(PHASER_PLUGIN_EXECUTABLE)
set(ARG_PLUGIN "${PHASER_PLUGIN_EXECUTABLE}")
elseif(TARGET protoc-gen-phaser)
set(ARG_PLUGIN $<TARGET_FILE:protoc-gen-phaser>)
list(APPEND tool_depends protoc-gen-phaser)
else()
message(FATAL_ERROR
"phaser_add_library(${name}) needs a host protoc-gen-phaser: "
"pass PLUGIN or set PHASER_PLUGIN_EXECUTABLE")
endif()
endif()

set(out_dir "${CMAKE_CURRENT_BINARY_DIR}/${name}_phaser")
file(MAKE_DIRECTORY "${out_dir}")
if(ARG_PACKAGE)
set(gen_dir "${out_dir}/${ARG_PACKAGE}/${name}")
set(gen_include "${ARG_PACKAGE}/${name}")
else()
set(gen_dir "${out_dir}/${name}")
set(gen_include "${name}")
endif()

set(proto_paths)
foreach(dir ${ARG_IMPORT_DIRS})
list(APPEND proto_paths "--proto_path=${dir}")
endforeach()

set(options "package_name=${ARG_PACKAGE},target_name=${name},frontend=${ARG_FRONTEND}")
if(ARG_ADD_NAMESPACE)
set(options "add_namespace=${ARG_ADD_NAMESPACE},${options}")
endif()

set(inputs)
set(outputs)
set(short_headers)
foreach(proto ${ARG_PROTOS} ${ARG_IMPORTS})
set(found)
foreach(dir ${ARG_IMPORT_DIRS})
if(NOT found AND EXISTS "${dir}/${proto}")
set(found "${dir}/${proto}")
endif()
endforeach()
if(NOT found)
message(FATAL_ERROR
"phaser_add_library(${name}): ${proto} is not in IMPORT_DIRS")
endif()
list(APPEND inputs "${found}")

string(REGEX REPLACE "\\.proto$" "" base "${proto}")
list(APPEND outputs "${gen_dir}/${base}.phaser.cc" "${gen_dir}/${base}.phaser.h")
if(proto IN_LIST ARG_PROTOS)
set(short_header "${out_dir}/${base}.phaser.h")
file(GENERATE OUTPUT "${short_header}"
CONTENT "#pragma once\n#include \"${gen_include}/${base}.phaser.h\"\n")
list(APPEND short_headers "${short_header}")
endif()
endforeach()

# The protos are named by import path. An absolute path could map to a
# second name when one import directory contains another.
add_custom_command(
OUTPUT ${outputs}
COMMAND ${ARG_PROTOC}
ARGS --plugin=protoc-gen-phaser=${ARG_PLUGIN}
--phaser_out=${options}:${out_dir}
${proto_paths}
${ARG_PROTOS} ${ARG_IMPORTS}
DEPENDS ${inputs} ${tool_depends}
WORKING_DIRECTORY "${out_dir}"
COMMENT "Generating phaser code for ${name}"
VERBATIM
)

add_library(${name} STATIC ${outputs} ${short_headers})
target_include_directories(${name} PUBLIC "${out_dir}")
target_link_libraries(${name} PUBLIC phaser_runtime)
if(ARG_COMPILE_OPTIONS)
target_compile_options(${name} PRIVATE ${ARG_COMPILE_OPTIONS})
endif()
endfunction()
12 changes: 10 additions & 2 deletions MODULE.bazel
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
module(
name = "phaser",
version = "2.1.2"
version = "3.0.0"
)

bazel_dep(name = "bazel_skylib", version = "1.9.0")
Expand All @@ -17,7 +17,15 @@ bazel_dep(name = "rules_shell", version = "0.8.0")
bazel_dep(name = "rules_pkg", version = "1.0.1")
bazel_dep(name = "zlib", version = "1.3.1.bcr.5")

bazel_dep(name = "cpp_toolbelt", version = "2.1.5")
bazel_dep(name = "cpp_toolbelt", version = "2.1.6")

# Remove once cpp_toolbelt 2.1.6 is in the Bazel Central Registry.
git_override(
module_name = "cpp_toolbelt",
commit = "86b45cdd3da46a97a4476be2f2a61909677284a2",
remote = "https://github.com/dallison/cpp_toolbelt.git",
)

bazel_dep(name = "coroutines", version = "3.3.2")

# protobuf pulls an older rules_go via gazelle; Bazel 9 needs a current rules_go.
Expand Down
2 changes: 0 additions & 2 deletions MODULE.bazel.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading