stride3d/stride · error · ArgumentNullException

Using the cache requires a database.

Error message

Using the cache requires a database.

What it means

The EffectCompilerCache constructor wraps an underlying compiler with a database-backed bytecode cache. A DatabaseFileProvider is mandatory — without it there is nowhere to read/write cached bytecode — so a null database produces this ArgumentNullException.

Solutions

  1. Create and pass a DatabaseFileProvider backed by your effect/obj virtual file system before constructing the cache.
  2. Verify the database initialization code runs before the compiler chain is built.
  3. If you truly have no database, use the underlying compiler directly instead of EffectCompilerCache.

Example fix

// before
var cache = new EffectCompilerCache(compiler, null, schedulerSelector);
// after
var database = new DatabaseFileProvider(VirtualFileSystemMountResult);
var cache = new EffectCompilerCache(compiler, database, schedulerSelector);
Defensive patterns

Strategy: validation

Validate before calling

if (database == null) throw new InvalidOperationException("Initialize the DatabaseFileProvider before constructing EffectCompilerCache");

Type guard

null

Try / catch

try { var cache = new EffectCompilerCache(compiler, database); } catch (ArgumentNullException ex) when (ex.ParamName == "database") { /* initialize database and retry */ }

Prevention

When it happens

Trigger: new EffectCompilerCache(compiler, null) — passing a null DatabaseFileProvider, e.g. when the file provider/database was not initialized or a factory returned null.

Common situations: Bootstrapping a game/service before the database file provider is created; DI containers failing to resolve the database; skipping database setup on headless build agents.

Related errors


AI-assisted analysis of stride3d/stride@96fad776d2 (2026-09-14). Data as JSON: /api/errors/d4f7e251438c75c1. Report an issue: GitHub.

Appendix: source

Thrown at sources/shaders/Stride.Shaders.Effects/Compiler/EffectCompilerCache.cs:56

        // Used when shader is compiled (esp. when CompileEffectAsynchronously is false, but also when true during some specific race conditions)
        private readonly Dictionary<ObjectId, EffectBytecodeCompilerResult> compiledShaders = new Dictionary<ObjectId, EffectBytecodeCompilerResult>();

        private readonly DatabaseFileProvider database;
        private readonly TaskSchedulerSelector taskSchedulerSelector;

        private int effectCompileCount;

        public bool CompileEffectAsynchronously { get; set; }

        /// <summary>
        /// If we have to compile a new shader, what kind of cache are we building?
        /// </summary>
        public EffectBytecodeCacheLoadSource CurrentCache { get; set; } = EffectBytecodeCacheLoadSource.DynamicCache;

        public EffectCompilerCache(EffectCompilerBase compiler, DatabaseFileProvider database, TaskSchedulerSelector taskSchedulerSelector = null) : base(compiler)
        {
            CompileEffectAsynchronously = true;
            this.database = database ?? throw new ArgumentNullException(nameof(database), "Using the cache requires a database.");
            this.taskSchedulerSelector = taskSchedulerSelector;
        }

        public override void ResetCache(HashSet<string> modifiedShaders)
        {
            // remove old shaders from cache
            lock (bytecodes)
            {
                base.ResetCache(modifiedShaders);
                RemoveObsoleteStoredResults(modifiedShaders);
            }

            // A compiled result is memoized per effect input hash, which doesn't change when a shader
            // file does. Left in place, the first reload would be the only one ever to take effect.
            lock (compilingShaders)
            {
                foreach (var key in compiledShaders
                    .Where(x => x.Value.Bytecode != null && IsBytecodeObsolete(x.Value.Bytecode, modifiedShaders))

View on GitHub (pinned to 96fad776d2)