Helps set up C# library projects that provide nodes to vvvv gamma — project directory structure, Initialization.cs with AssemblyInitializer, service registra...
A node library is a project that provides multiple nodes to vvvv gamma as a distributable package. This skill covers the project-level concerns: directory structure, naming conventions, category organization, service registration, and node factories.
For writing individual node classes (ProcessNode, Update, pins, change detection), see vvvv-custom-nodes. For consuming services inside node constructors (IFrameClock, Game, logging), see vvvv-custom-nodes/services.md.
vvvv recognizes a directory as a library when the folder name, .vl file, and .nuspec all share the same name:
VL.MyLibrary/ # Folder name = package name
├── VL.MyLibrary.vl # .vl document — MUST match folder name
├── VL.MyLibrary.nuspec # NuGet spec — MUST match folder name
├── lib/
│ └── net8.0/ # Compiled DLLs go here
│ └── VL.MyLibrary.dll
├── src/
│ ├── Initialization.cs # [assembly:] attributes + AssemblyInitializer
│ ├── Nodes/
│ │ ├── MyProcessNode.cs # [ProcessNode] classes
│ │ └── MyOperations.cs # Static methods (stateless nodes)
│ ├── Services/
│ │ └── MyService.cs # Per-app singletons
│ └── VL.MyLibrary.csproj
├── shaders/ # Optional: SDSL shaders (auto-discovered)
│ └── MyEffect_TextureFX.sdsl
└── help/ # Optional: .vl help patches
└── HowTo Use MyNode.vl
Critical conventions:
.vl file, and .nuspec must be identical (e.g., all VL.MyLibrary).csproj must output DLLs to lib/net8.0/ relative to the package root.vl file within a package should reference a .csproj — this forces the package into editable modeThe .csproj must compile into the library's lib/net8.0/ folder:
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<OutputPath>..\..\lib\net8.0\</OutputPath>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
</PropertyGroup>
Every node library needs assembly-level attributes. Combine in one file:
using VL.Core;
using VL.Core.CompilerServices;
using VL.Core.Import;
// Required: tells vvvv to scan this assembly for nodes
[assembly: ImportAsIs(Namespace = "MyCompany.MyLibrary", Category = "MyLibrary")]
// Optional: register services before any node runs
[assembly: AssemblyInitializer(typeof(MyCompany.MyLibrary.Initialization))]
namespace MyCompany.MyLibrary;
public sealed class Initialization : AssemblyInitializer<Initialization>
{
public override void Configure(AppHost appHost)
{
var services = appHost.Services;
// Register per-app singletons (created lazily on first access)
services.RegisterService<MyService>(serviceProvider =>
{
return new MyService(serviceProvider);
});
}
}
| Parameter | Purpose | Example |
|---|---|---|
Namespace | C# namespace to scan for public types | "VL.MyLibrary" |
Category | Node browser category for all discovered nodes | "MyLibrary" |
Without parameters ([assembly: ImportAsIs]), vvvv scans all namespaces and strips one namespace level to derive the category. The parameterized form gives explicit control over library organization.
Namespace → Category mapping: C# namespaces map to dot-separated node browser categories. Classes in VL.MyLibrary.Particles appear under MyLibrary.Particles in the node browser (the root namespace specified in ImportAsIs is stripped).
Override the category for individual types:
[assembly: ImportType(typeof(SpecialNode), Category = "MyLibrary.Advanced")]
Use this to place specific nodes into different categories than their namespace would suggest.
Services are registered in Configure(AppHost) and consumed by nodes via NodeContext. This section covers registration only — for consumption patterns, see vvvv-custom-nodes/services.md.
services.RegisterService<MyService>(serviceProvider =>
{
// Created lazily on first GetService<MyService>() call
return new MyService(serviceProvider);
});
When the service wraps a resource that needs explicit disposal:
services.RegisterService<IResourceProvider<MyGPUService>>(serviceProvider =>
{
var gameProvider = serviceProvider.GetService<IResourceProvider<Game>>();
return gameProvider.Bind(game =>
{
var service = MyGPUService.Create(game);
return ResourceProvider.Return(service, disposeAction: s => s?.Dispose());
});
});
Register programmatic node generation for dynamic node sets:
public override void Configure(AppHost appHost)
{
// Dynamic node factory from shader files or other sources
appHost.RegisterNodeFactory("VL.MyLibrary.ShaderNodes",
init: MyShaderNodeFactory.Init);
}
Use node factories when nodes are generated from external files (shaders, configs) rather than written as C# classes. For details, see the vvvv Node Factories docs.
Provide typed accessors for your services:
public static class MyLibraryExtensions
{
public static MyService? GetMyService(this ServiceRegistry services)
=> services.GetService(typeof(MyService)) as MyService;
public static MyService? GetMyService(this IServiceProvider services)
=> services.GetService(typeof(MyService)) as MyService;
}
Full library .csproj with output to lib/net8.0/:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<OutputPath>..\..\lib\net8.0\</OutputPath>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="VL.Core" Version="2025.7.*" />
<PackageReference Include="VL.Core.Import" Version="2025.7.*" />
<!-- For Stride integration: -->
<PackageReference Include="VL.Stride.Runtime" Version="2025.7.*" />
</ItemGroup>
</Project>
Match VL package versions to your vvvv installation version. The OutputPath places compiled DLLs in the library's lib/net8.0/ folder where vvvv expects to find them.
Library initialization with service registration and node factory:
[assembly: AssemblyInitializer(typeof(Initialization))]
[assembly: ImportAsIs(Namespace = "VL.MyRendering", Category = "MyRendering")]
public sealed class Initialization : AssemblyInitializer<Initialization>
{
public override void Configure(AppHost appHost)
{
appHost.Services.RegisterService<CustomGameSystem>(sp =>
{
var vlGame = sp.GetService<VLGame>();
if (vlGame == null) return null!;
var customGame = CustomGameSystem.Create(vlGame, sp);
vlGame.GameSystems.Add(customGame);
return customGame;
});
// Dynamic node factory from shader files
appHost.RegisterNodeFactory("VL.MyRendering.ShaderNodes",
init: ShaderNodeFactory.Init);
}
}
For naming conventions, pin rules, aspects, and standard types, see design-guidelines.md. For publishing NuGets, help patches, and library structure, see publishing.md. For complete real-world examples (VL.IO.MQTT, VL.Audio), see examples.md.
ZIP package — ready to use