Runtime Streaming Systems
ProStream's runtime streaming systems are built on Unity's DOTS (Data-Oriented Technology Stack) and use ECS (Entity Component System) for high-performance, distance-based scene streaming.
Overview
These systems automatically load and unload SubScenes based on proximity to a loading trigger (typically the player or camera).
Key Runtime Components
- SubSceneLoadingSystem - Handles loading SubScenes and sections
- SubSceneUnloadingSystem - Handles unloading SubScenes and sections
- StreamingManager - Coordinates streaming operations
- ProStreamSystem - Base system infrastructure
- Loading Trigger - Reference position for distance calculations (GameObject)
Core Concepts
Distance-Based Streaming
ProStream calculates the distance between the Loading Trigger (your player/camera) and each SubScene. When the distance falls within configured thresholds, sections are loaded or unloaded.
Distance Calculation Example:
Player Position: (0, 0, 0)
SubScene Position: (100, 0, 50)
Distance: ~111 units
If Section "LargeObjects" loads at 150m:
→ Section IS loaded (111m < 150m)
If Section "SmallObjects" loads at 50m:
→ Section NOT loaded (111m > 50m)Section-Based Loading
Each SubScene contains multiple sections (Ground, LargeObjects, SmallObjects, etc.). Sections can load/unload independently based on their configured distances.
Benefits:
- Fine-grained control over what loads when
- Reduced memory usage (only load what's visible)
- Improved performance (fewer objects to process)
- Seamless streaming (no loading screens)
SubSceneLoadingSystem
Location: Runtime/Scripts/Streaming/SubSceneLoadingSystem.cs
Purpose
Manages the loading of SubScenes and their sections based on distance from the loading trigger.
How It Works
Every Frame (Update):
1. Calculate distance to each SubScene
2. Determine which sections should be loaded
3. Queue loading requests for new sections
4. Execute loading jobs asynchronously
5. Update loaded section trackingState Machine
The loading system maintains internal states for each SubScene:
- Unloaded - SubScene not in memory
- Loading - SubScene being loaded
- Loaded - SubScene fully loaded and active
- Streaming - Sections loading/unloading dynamically
Unloaded → Loading → Loaded → Streaming
↓ ↓
←──────────────────────────────
(Distance exceeds threshold)ECS Queries
The system uses ECS queries to efficiently process SubScenes:
// Query for SubScenes that need loading
EntityQuery loadingQuery = GetEntityQuery(
ComponentType.ReadOnly<SubSceneData>(),
ComponentType.ReadOnly<WorldTransform>(),
ComponentType.Exclude<LoadedSection>()
);
// Query for SubScenes with sections to load
EntityQuery sectionQuery = GetEntityQuery(
ComponentType.ReadOnly<SceneSection>(),
ComponentType.ReadOnly<SectionDistance>()
);Jobs for Performance
Loading operations run as parallel jobs using Unity's Job System with Burst compilation:
[BurstCompile]
public struct DistanceCalculationJob : IJobParallelFor
{
[ReadOnly] public float3 loadingTriggerPosition;
[ReadOnly] public NativeArray<float3> subScenePositions;
public NativeArray<float> distances;
public void Execute(int index)
{
distances[index] = math.distance(
loadingTriggerPosition,
subScenePositions[index]
);
}
}SubSceneUnloadingSystem
Location: Runtime/Scripts/Streaming/SubSceneUnloadingSystem.cs
Purpose
Manages the unloading of SubScenes and sections when they exceed distance thresholds or when triggered manually.
How It Works
Every Frame (Update):
1. Check loaded sections against distance thresholds
2. Identify sections to unload
3. Queue unloading requests
4. Execute unloading jobs
5. Release resources
6. Update tracking dataUnloading Strategy - Hysteresis
ProStream uses slightly different thresholds for loading vs unloading to prevent "flickering" (rapid load/unload cycles).
Example:
- Load at: 150m
- Unload at: 160m (10m hysteresis buffer)
This prevents objects from loading/unloading repeatedly when the player hovers around the threshold.
Configuration:
public void ConfigureHysteresis(float buffer)
{
StreamingManager.Instance.HysteresisBuffer = buffer;
}Memory Management
The unloading system ensures proper cleanup:
- Destroys entity data
- Releases scene references
- Clears cached data
- Triggers garbage collection hints (when appropriate)
StreamingManager
Location: Runtime/Scripts/MonoBehaviour/StreamingManager.cs
Purpose
High-level coordinator for all streaming operations. Acts as the bridge between MonoBehaviour world and ECS systems.
Key Responsibilities
Loading Trigger Management
- Tracks the reference position (player/camera)
- Updates trigger position every frame
- Provides position to ECS systems
System Coordination
- Enables/disables streaming systems
- Controls streaming behavior
- Handles streaming state
Configuration Management
- Applies streaming settings
- Manages distance thresholds
- Handles persistent sections
Debug Visualization
- Displays streaming boundaries (gizmos)
- Shows loaded/unloaded sections
- Provides runtime feedback
Setup
StreamingManager is automatically created after SubScene creation. You can also add it manually.
Manual Setup:
- Create empty GameObject in scene
- Add
StreamingManagercomponent - Assign Loading Trigger (player/camera)
- Configure settings in Inspector
Inspector Settings
Loading Trigger
- Reference to player/camera Transform
- Used for distance calculations
- Auto-finds Camera.main if not set
Streaming Settings
- Enable/disable streaming
- Hysteresis buffer distance
- Update frequency
- Debug visualization options
Section Configuration
- Override section distances
- Enable/disable specific sections
- Persistent section settings
Runtime Behavior
Initialization
When Play Mode starts:
- StreamingManager initializes
- ECS systems are created
- SubScene data is loaded
- Initial sections are loaded (if within range)
- Streaming begins
During Play
Every frame:
- StreamingManager updates Loading Trigger position
- SubSceneLoadingSystem calculates distances
- Sections within threshold are queued for loading
- SubSceneUnloadingSystem checks loaded sections
- Sections beyond threshold are queued for unloading
- Jobs execute asynchronously
- Scene updates reflect changes
Hierarchy During Play Mode
YourScene (Main)
├── SceneConnector
├── StreamingManager
├── SubSceneRoot
│ ├── YourScene_Quad_0_0 (SubScene - Loaded)
│ │ ├── Section_Ground (Loaded)
│ │ ├── Section_LargeObjects (Loaded)
│ │ └── Section_SmallObjects (Not Loaded)
│ ├── YourScene_Quad_0_1 (SubScene - Not Loaded)
│ └── ...
└── CameraPerformance Optimization
Distance Calculation
- Uses Burst-compiled jobs for parallel processing
- Calculates squared distance (avoids sqrt when possible)
- Batches calculations per frame
Loading/Unloading
- Asynchronous operations don't block main thread
- Spreads loading across multiple frames
- Prioritizes closest sections first
Memory Management
- Only loaded sections consume memory
- Unloaded sections are fully released
- Hysteresis prevents memory thrashing
Debugging Runtime Streaming
Console Logging
Enable streaming debug logs in StreamingManager:
[Streaming] Loading section: MyScene_Quad_0_0.Section_Ground (distance: 45m)
[Streaming] Unloading section: MyScene_Quad_2_2.Section_SmallObjects (distance: 165m)Gizmos Visualization
Enable gizmos in Scene view during Play Mode:
- Green spheres = Loaded sections
- Red spheres = Unloaded sections
- Yellow lines = Loading trigger radius
Profiler
Monitor streaming performance:
- CPU Usage - Check system overhead
- Memory - Watch for memory leaks
- Loading Time - Measure section load times
Common Runtime Issues
Sections not loading
- Check Loading Trigger is assigned
- Verify distances are configured correctly
- Ensure StreamingManager is enabled
- Check Console for errors
Sections loading too late
- Increase section load distances
- Reduce hysteresis buffer
- Check for performance bottlenecks
Memory usage too high
- Reduce section load distances
- Split large sections into smaller ones
- Unload unused sections manually
Flickering (rapid load/unload)
- Increase hysteresis buffer
- Adjust distance thresholds
- Check Loading Trigger movement speed
Advanced Configuration
Custom Loading Trigger
public class CustomLoadingTrigger : MonoBehaviour
{
void Update()
{
// Update StreamingManager with custom position
StreamingManager.Instance.SetLoadingTriggerPosition(transform.position);
}
}Manual Section Control
// Manually load a specific section
StreamingManager.Instance.LoadSection("MyScene_Quad_0_0", "Ground");
// Manually unload a specific section
StreamingManager.Instance.UnloadSection("MyScene_Quad_2_2", "SmallObjects");Event Callbacks
// Subscribe to streaming events
StreamingManager.Instance.OnSectionLoaded += HandleSectionLoaded;
StreamingManager.Instance.OnSectionUnloaded += HandleSectionUnloaded;
void HandleSectionLoaded(string subSceneName, string sectionName)
{
Debug.Log($"Loaded: {subSceneName}.{sectionName}");
}See Also
- Streaming Layers - Configure layer distances
- SubScene Creation - How SubScenes are created
- Standard Workflow - Complete setup guide
- Troubleshooting - Common issues and solutions
