A B C D E F
1 FruitType ID Price Name Tag Stock
2 map<int32,Fruit> map<int32,Item> int32 string string int32
3 Outer key Inner key Indexed field Display Tag Stock
4 1 100 99 Apple fresh 120
5 1 101 79 Pear fresh 80
6 2 200 99 Berry frozen 40
Tableau Tableau Loader | v0.6.0 | Released June 11, 2026

RELEASE NOTES

v0.6.0

First-class C# loader plugin, Protobuf Editions 2023/2024 across all three plugins, a unified make.py build pipeline replacing init.bat / init.sh / prepare.bat, and vcpkg-managed protobuf with a quarterly-pinned matrix.

+ 7 Features ○ 6 Bug Fixes ⟲ 3 Refactors | Go · C++17 · C# (Unity 2022.3 LTS / .NET 8) · Editions 2023/2024 · Go 1.26
01

New Features

NEW PLUGIN

C# Loader Plugin

New protoc-gen-csharp-tableau-loader plugin. Targets Unity 2022.3 LTS / .NET 8, generates *.pc.cs. Same Hub / Messager / Load API surface as Go & C++, including patch loading, MessagerOptions, custom ReadFunc/LoadFunc, and a thread-safe Atomic<T>-backed Hub.

▸ Program.cs
var hub  = new Tableau.Hub();
var opts = new Tableau.Load.Options {
    IgnoreUnknownFields = true,
    PatchDirs           = new() { "./patch" },
};
hub.Load("./conf", Format.JSON, opts);
var conf = hub.GetItemConf();
NEW EDITIONS

Protobuf Editions 2023 & 2024

All three plugins (Go / C++ / C#) emit code that compiles cleanly under edition = "2023" and "2024" alongside proto2 / proto3. The build toolchain in init.sh / init.bat was modernized to install a matching protoc.

▸ schema.proto
// proto2 / proto3 still supported
// new: Editions 2023 & 2024
edition = "2024";
package protoconf;
message ItemConf { ... }
NEW PATCH · TESTS

Cross-Language Patch + Real Test Suites

Three real test suites — Go (testing), C++ (gtest), C# (xUnit) — exercise the same patch scenarios with golden-file parity. Standalone main.go / main.cpp / Program.cs demos were retired.

▸ patch matrix
✓ go     main_test.go::Test_Patch_*           PASS
✓ cpp    tests/patch_test.cpp PatchConf_*     PASS
✓ csharp tests/PatchTests.cs PatchConf_*       PASS
// All three load the same testdata/conf + testdata/patchconf
// and compare to testdata/patchresult/ goldens.
NEW BUILD

Unified make.py Pipeline

One Python 3.10+ stdlib-only entry point replaces init.sh, init.bat, and prepare.bat. Subcommands: setup, generate, build, test, clean, env. CI runs the same script as developers.

▸ shell
$ python3 make.py setup --lang all
$ python3 make.py test  --lang go
$ python3 make.py test  --lang cpp     # vcpkg-installed protobuf
$ python3 make.py test  --lang csharp -k HubTests
$ python3 make.py test  --lang cpp     --protobuf-version 3.21.12
NEW DEV ENV

Devcontainer & buf Codegen

Reproducible dev environment via .devcontainer/: VS Code → "Reopen in Container" gives the same Go, buf, vcpkg, and protobuf versions used in CI. buf generate replaces the per-language gen.sh / gen.bat scripts in every test workspace.

▸ .devcontainer/versions.env
GO_VERSION=1.24.0
BUF_VERSION=1.67.0
DOTNET_VERSION=8.0
DEFAULT_VARIANT=modern
MODERN_PROTOBUF_VERSION=6.33.4
LEGACY_V3_PROTOBUF_VERSION=3.21.12
NEW LOGGING

Dual-Mode Protobuf Log Handler (C++)

The C++ runtime now installs a Tableau log sink on libprotobuf — routing [libprotobuf] messages into the Tableau logger. Compile-time TABLEAU_PB_LOG_LEGACY picks legacy SetLogHandler (protobuf ≤ 3.x) or modern absl::LogSink (protobuf ≥ 4.x with Abseil).

▸ util.pc.cc
#if TABLEAU_PB_LOG_LEGACY
  // SetLogHandler(ProtobufLogHandler) on protobuf 3.x
#else
  // class ProtobufAbslLogSink : public absl::LogSink {
  //   void Send(const absl::LogEntry& entry) override;
  // };
#endif
ATOM_LOGGER_CALL(tableau::log::DefaultLogger(), lvl,
                 "[libprotobuf %s:%d] %s", ...);
NEW GO 1.26

Self-Referential Generics for TreeMap

pkg/treemap/redblacktree now uses self-referential generics on Go ≥ 1.26 (Lesser[T Lesser[T]]), with a //go:build !go1.26 fallback to keep older toolchains compiling. Tightens type safety for FindIter, UpperBound, LowerBound.

▸ pkg/treemap/redblacktree/lesser_go126.go
//go:build go1.26

package redblacktree

type Lesser[T Lesser[T]] interface {
    comparable
    Less(other T) bool
}
02

Bug Fixes

Index field in a list under upper maps now resolves correctly (#158)

Reworked LevelMessage to track MapDepth + a new LeveledContainerDepth(). List levels under a parent map now generate the correct number of leveled finders (FindItem1, FindItem2, …). Adds Fruit6Conf.json + 543 lines of index_test.go.

INDEX

Index parser skips oneof branches when walking levels (#156)

Three-line fix in internal/index/descriptor.go: when iterating a message's fields, descriptors with ContainingOneof() != nil are skipped, removing spurious "field not found" errors caused by oneof name conflicts.

INDEX

Field-name case conversion + cross-platform generation order (#153)

New underscoresToCamelCase helper unifies property naming across all three plugins; introduces internal/xproto.SplitShards so generation order is identical on Linux / macOS / Windows. New StrcaseConf regression suite covers UserID, V2Ray, XCoordinate, etc.

CODEGEN

Windows prepare.bat CMake 3.x install + admin elevation prompt (#152, #154)

Bootstrap script now winget-installs the right CMake major version and re-launches itself elevated when needed. Subsequent commits absorbed it into make.py setup.

WINDOWS

Templated includes use original proto names; OrderedMap accessors adjusted (#151)

Generated C++ hub.pc.cc / hub_shard.pc.cc templates emit the original (non-snake-cased) include names, and the OrderedMap accessor in C++ & C# generators only fires when there's a top-level map.

TEMPLATE

C++ GetLastLoadedTime no longer marked inline (#146)

The inline keyword on a non-trivial accessor caused ODR violations when the symbol was referenced from multiple translation units; removing it makes the link-time behavior portable.

CPP
03

Toolchain & Dependencies

The biggest non-feature change in v0.6.0 is build-system modernization. The bundled third_party/_submodules/protobuf submodule was removed in favor of vcpkg-managed protobuf with a quarterly-pinned VCPKG_BASELINE_COMMIT. Two variants ship out of the box:

DEFAULT_VARIANT=modern

protobuf 6.33.4

vcpkg snapshot pinned to 56bb2411… (tip of 2026.04.27).

DEFAULT_VARIANT=legacy_v3

protobuf 3.21.12 (Linux-only smoke)

vcpkg snapshot from 2023-01-15 (6245ce44…).

+ vcpkg-managed protobuf - third_party/_submodules/protobuf (removed) + go-udiff (extracted to pkg/udiff) - pmezard/go-difflib (replaced) + buf v1.67.0 (CI proto generation) + Editions 2023 / 2024 + Go 1.24 (devcontainer) · Go 1.26 build tag + .NET 8.0 SDK + tableau v0.16.0 + submodule recursive checkout in CI
Migration note. If you previously cloned with --recurse-submodules for the bundled protobuf, drop that flag and run python3 make.py setup --lang all (or open the repo in the devcontainer). The script bootstraps vcpkg at VCPKG_BASELINE_COMMIT, installs Go / buf / .NET pinned to .devcontainer/versions.env, and is idempotent.
Heads-up. init.bat, init.sh, and the original prepare.bat were deleted. If your CI or local scripts call them, switch to python3 make.py.
04

Examples

FIX · INDEX (#158)

Index on a list nested under a parent map

Consider Fruit6Conf: an outer map keyed by FruitType whose values contain a list of items, with the index Price<ID> and ordered index Price<ID>@OrderedFruit. v0.5.0 generated the wrong number of leveled containers because MapDepth alone didn't capture "list under map". v0.6.0 introduces LeveledContainerDepth(), which adds 1 for non-map levels that still need the parent map's container.

▸ Index.xlsx — Fruit6Conf

A B C
1 FruitType ID Price
2 map<int32,Fruit> [Item] int32
3 Outer map key List of items (vertical) Indexed by Price<ID>
4 1 99
5 1 79
6 2 99
✗ Before (v0.5.0)
// list under map → 0 leveled finders generated
// FindItem1(fruitType, price) absent
items := mgr.FindItem(99) // returns only 1 fruit type
✓ After (v0.6.0)
// LCD = MapDepth + 1 → 1 leveled finder
items := mgr.FindItem1(/*fruitType*/ 1, 99)
// scoped to the parent map key
FIX · ONEOF (#156)

Oneof branches no longer break level walking

When a level message embeds a oneof, the parser previously tried to resolve oneof case fields by name and conflicted with regular fields. The fix skips any descriptor whose ContainingOneof() is non-nil.

▸ internal/index/descriptor.go (parseCols)
for i := 0; i < md.Fields().Len(); i++ {
    fd := md.Fields().Get(i)
    if fd.ContainingOneof() != nil {
        continue  // skip oneof case fields
    }
    // ... existing logic
}
NEW · MULTI-LANG

One schema, three loaders, identical patch tests

From a single .proto set, v0.6.0 generates idiomatic Go, C++17, and C# loaders — and three real test suites (testing, gtest, xUnit) running the same PatchConf scenarios against shared testdata/ goldens.

▸ go: main_test.go
func prepareHub(t *testing.T) *hub.MyHub {
    h := hub.NewMyHub()
    h.Load("../testdata/conf/",
        format.JSON,
        load.IgnoreUnknownFields())
    return h
}
▸ cpp: tests/patch_test.cpp
TEST_F(PatchTest, RecursivePatchConf) {
    auto opts = NewOptions();
    opts->patch_dirs = {
        test::TestPaths::PatchConf().string()};
    Hub::Instance().Load(...);
}
▸ csharp: PatchTests.cs
[Fact]
public void PatchReplaceConf() {
    var opts = new Load.Options {
        PatchDirs = new() { TestPaths.PatchConfDir }
    };
    var hub = new Hub();
    hub.Load(...);
}
NEW · BUILD

A single Python script for the whole flow

make.py uses Python 3.10 stdlib only. It mounts vcpkg, sources vcvarsall.bat per-subprocess on Windows (your shell PATH/INCLUDE/LIB are never mutated), and is itself unit-tested via test_make.py + testing-make.yml.

▸ shell — typical contributor flow
# one-time host toolchain (no-op inside devcontainer)
$ python3 make.py setup --lang all

# per-language
$ python3 make.py test --lang go     -k Test_ActivityConf_OrderedMap
$ python3 make.py test --lang cpp    --cxx-std 20 --cxx-compiler clang
$ python3 make.py test --lang cpp    --protobuf-version 3.21.12
$ python3 make.py test --lang csharp -k HubTests

# diagnostic JSON (paths, versions, env probes)
$ python3 make.py env