yum-mirror/slang

Making it easier to work with shaders

git clone https://git.yummers.dev/yum-mirror/slang

Ellie HermaszewskaMove switch statement bodies to their own lines (#5493)b118451e3

master
33.6 KiB1145 linesraw
1#include "slang-file-system.h"
2
3#include "../core/slang-io.h"
4#include "../core/slang-string-util.h"
5#include "slang-com-ptr.h"
6
7namespace Slang
8{
9
10SLANG_FORCE_INLINE static SlangResult _checkExt(FileSystemStyle style)
11{
12    return Index(style) >= Index(FileSystemStyle::Ext) ? SLANG_OK : SLANG_E_NOT_IMPLEMENTED;
13}
14SLANG_FORCE_INLINE static SlangResult _checkMutable(FileSystemStyle style)
15{
16    return Index(style) >= Index(FileSystemStyle::Mutable) ? SLANG_OK : SLANG_E_NOT_IMPLEMENTED;
17}
18
19SLANG_FORCE_INLINE static bool _canCast(FileSystemStyle style, const Guid& guid)
20{
21    if (guid == ISlangUnknown::getTypeGuid() || guid == ISlangCastable::getTypeGuid() ||
22        guid == ISlangFileSystem::getTypeGuid())
23    {
24        return true;
25    }
26    else if (guid == ISlangFileSystemExt::getTypeGuid())
27    {
28        return Index(style) >= Index(FileSystemStyle::Ext);
29    }
30    else if (guid == ISlangMutableFileSystem::getTypeGuid())
31    {
32        return Index(style) >= Index(FileSystemStyle::Mutable);
33    }
34    return false;
35}
36
37static FileSystemStyle _getFileSystemStyle(ISlangFileSystem* system, ComPtr<ISlangFileSystem>& out)
38{
39    SLANG_ASSERT(system);
40
41    FileSystemStyle style = FileSystemStyle::Load;
42
43    if (SLANG_SUCCEEDED(
44            system->queryInterface(ISlangMutableFileSystem::getTypeGuid(), (void**)out.writeRef())))
45    {
46        style = FileSystemStyle::Mutable;
47    }
48    else if (SLANG_SUCCEEDED(system->queryInterface(
49                 ISlangFileSystemExt::getTypeGuid(),
50                 (void**)out.writeRef())))
51    {
52        style = FileSystemStyle::Ext;
53    }
54    else
55    {
56        style = FileSystemStyle::Load;
57        out = system;
58    }
59
60    SLANG_ASSERT(out);
61    return style;
62}
63
64// Calcuate a combined path, just using Path:: string processing
65static SlangResult _calcCombinedPath(
66    SlangPathType fromPathType,
67    const char* fromPath,
68    const char* path,
69    ISlangBlob** pathOut)
70{
71    String relPath;
72    switch (fromPathType)
73    {
74    case SLANG_PATH_TYPE_FILE:
75        {
76            relPath = Path::combine(Path::getParentDirectory(fromPath), path);
77            break;
78        }
79    case SLANG_PATH_TYPE_DIRECTORY:
80        {
81            relPath = Path::combine(fromPath, path);
82            break;
83        }
84    }
85
86    *pathOut = StringUtil::createStringBlob(relPath).detach();
87    return SLANG_OK;
88}
89
90/* !!!!!!!!!!!!!!!!!!!!!!!!!!!!! OSFileSystem !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!*/
91
92/* static */ OSFileSystem OSFileSystem::g_load(FileSystemStyle::Load);
93/* static */ OSFileSystem OSFileSystem::g_ext(FileSystemStyle::Ext);
94/* static */ OSFileSystem OSFileSystem::g_mutable(FileSystemStyle::Mutable);
95
96void* OSFileSystem::castAs(const Guid& guid)
97{
98    if (auto ptr = getInterface(guid))
99    {
100        return ptr;
101    }
102    return getObject(guid);
103}
104
105ISlangUnknown* OSFileSystem::getInterface(const Guid& guid)
106{
107    return _canCast(m_style, guid) ? static_cast<ISlangFileSystem*>(this) : nullptr;
108}
109
110void* OSFileSystem::getObject(const Guid& guid)
111{
112    SLANG_UNUSED(guid);
113    return nullptr;
114}
115
116static String _fixPathDelimiters(const char* pathIn)
117{
118#if SLANG_WINDOWS_FAMILY
119    return pathIn;
120#else
121    // To allow windows style \ delimiters on other platforms, we convert to our standard delimiter
122    String path(pathIn);
123    return StringUtil::calcCharReplaced(pathIn, '\\', Path::kPathDelimiter);
124#endif
125}
126
127SlangResult OSFileSystem::getFileUniqueIdentity(const char* pathIn, ISlangBlob** outUniqueIdentity)
128{
129    SLANG_RETURN_ON_FAIL(_checkExt(m_style));
130
131    // By default we use the canonical path to uniquely identify a file
132    return getPath(PathKind::Canonical, pathIn, outUniqueIdentity);
133}
134
135SlangResult OSFileSystem::getPath(PathKind pathKind, const char* path, ISlangBlob** outPath)
136{
137    SLANG_RETURN_ON_FAIL(_checkExt(m_style));
138
139    switch (pathKind)
140    {
141    case PathKind::OperatingSystem:
142    case PathKind::Display:
143        {
144            // It's possible canonical path fail...
145            if (SLANG_SUCCEEDED(getPath(PathKind::Canonical, path, outPath)))
146            {
147                return SLANG_OK;
148            }
149            // If so try simplified
150            return getPath(PathKind::Simplified, path, outPath);
151        }
152    case PathKind::Canonical:
153        {
154            String canonicalPath;
155            SLANG_RETURN_ON_FAIL(Path::getCanonical(_fixPathDelimiters(path), canonicalPath));
156            *outPath = StringUtil::createStringBlob(canonicalPath).detach();
157            return SLANG_OK;
158        }
159    case PathKind::Simplified:
160        {
161            String simplifiedPath = Path::simplify(path);
162            *outPath = StringUtil::createStringBlob(simplifiedPath).detach();
163            return SLANG_OK;
164        }
165    }
166
167    return SLANG_E_NOT_AVAILABLE;
168}
169
170SlangResult OSFileSystem::calcCombinedPath(
171    SlangPathType fromPathType,
172    const char* fromPath,
173    const char* path,
174    ISlangBlob** pathOut)
175{
176    SLANG_RETURN_ON_FAIL(_checkExt(m_style));
177
178    // Don't need to fix delimiters - because combine path handles both path delimiter types
179    return _calcCombinedPath(fromPathType, fromPath, path, pathOut);
180}
181
182SlangResult SLANG_MCALL OSFileSystem::getPathType(const char* pathIn, SlangPathType* pathTypeOut)
183{
184    SLANG_RETURN_ON_FAIL(_checkExt(m_style));
185
186    return Path::getPathType(_fixPathDelimiters(pathIn), pathTypeOut);
187}
188
189
190SlangResult OSFileSystem::loadFile(char const* pathIn, ISlangBlob** outBlob)
191{
192    // Default implementation that uses the `core` libraries facilities for talking to the OS
193    // filesystem.
194    //
195    // TODO: we might want to conditionally compile these in, so that
196    // a user could create a build of Slang that doesn't include any OS
197    // filesystem calls.
198
199    const String path = _fixPathDelimiters(pathIn);
200    if (!File::exists(path))
201    {
202        return SLANG_E_NOT_FOUND;
203    }
204
205    ScopedAllocation alloc;
206    SLANG_RETURN_ON_FAIL(File::readAllBytes(path, alloc));
207    *outBlob = RawBlob::moveCreate(alloc).detach();
208    return SLANG_OK;
209}
210
211SlangResult OSFileSystem::enumeratePathContents(
212    const char* path,
213    FileSystemContentsCallBack callback,
214    void* userData)
215{
216    SLANG_RETURN_ON_FAIL(_checkExt(m_style));
217
218    struct Visitor : Path::Visitor
219    {
220        void accept(Path::Type type, const UnownedStringSlice& filename) SLANG_OVERRIDE
221        {
222            m_buffer.clear();
223            m_buffer.append(filename);
224
225            SlangPathType pathType;
226            switch (type)
227            {
228            case Path::Type::File:
229                pathType = SLANG_PATH_TYPE_FILE;
230                break;
231            case Path::Type::Directory:
232                pathType = SLANG_PATH_TYPE_DIRECTORY;
233                break;
234            default:
235                return;
236            }
237
238            m_callback(pathType, m_buffer.getBuffer(), m_userData);
239        }
240
241        Visitor(FileSystemContentsCallBack callback, void* userData)
242            : m_callback(callback), m_userData(userData)
243        {
244        }
245        StringBuilder m_buffer;
246        FileSystemContentsCallBack m_callback;
247        void* m_userData;
248    };
249
250    Visitor visitor(callback, userData);
251    Path::find(path, nullptr, &visitor);
252
253    return SLANG_OK;
254}
255
256SlangResult OSFileSystem::saveFile(const char* pathIn, const void* data, size_t size)
257{
258    SLANG_RETURN_ON_FAIL(_checkMutable(m_style));
259    const String path = _fixPathDelimiters(pathIn);
260    FileStream stream;
261    SLANG_RETURN_ON_FAIL(
262        stream.init(pathIn, FileMode::Create, FileAccess::Write, FileShare::ReadWrite));
263    SLANG_RETURN_ON_FAIL(stream.write(data, size));
264    return SLANG_OK;
265}
266
267SlangResult OSFileSystem::saveFileBlob(const char* path, ISlangBlob* dataBlob)
268{
269    if (!dataBlob)
270    {
271        return SLANG_E_INVALID_ARG;
272    }
273    return saveFile(path, dataBlob->getBufferPointer(), dataBlob->getBufferSize());
274}
275
276SlangResult OSFileSystem::remove(const char* path)
277{
278    SLANG_RETURN_ON_FAIL(_checkMutable(m_style));
279    return Path::remove(path);
280}
281
282SlangResult OSFileSystem::createDirectory(const char* path)
283{
284    SLANG_RETURN_ON_FAIL(_checkMutable(m_style));
285    return Path::createDirectory(path);
286}
287
288// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! CacheFileSystem !!!!!!!!!!!!!!!!!!!!!!!!!!!
289
290/* static */ const Result CacheFileSystem::s_compressedResultToResult[] = {
291    SLANG_E_UNINITIALIZED,
292    SLANG_OK,            ///< Ok
293    SLANG_E_NOT_FOUND,   ///< File not found
294    SLANG_E_CANNOT_OPEN, ///< CannotOpen,
295    SLANG_FAIL,          ///< Fail
296};
297
298/* static */ CacheFileSystem::CompressedResult CacheFileSystem::toCompressedResult(Result res)
299{
300    if (SLANG_SUCCEEDED(res))
301    {
302        return CompressedResult::Ok;
303    }
304    switch (res)
305    {
306    case SLANG_E_CANNOT_OPEN:
307        return CompressedResult::CannotOpen;
308    case SLANG_E_NOT_FOUND:
309        return CompressedResult::NotFound;
310    default:
311        return CompressedResult::Fail;
312    }
313}
314
315void* CacheFileSystem::castAs(const Guid& guid)
316{
317    if (auto ptr = getInterface(guid))
318    {
319        return ptr;
320    }
321    return getObject(guid);
322}
323
324void* CacheFileSystem::getInterface(const Guid& guid)
325{
326    if (_canCast(FileSystemStyle::Ext, guid))
327    {
328        return static_cast<ISlangFileSystemExt*>(this);
329    }
330    return nullptr;
331}
332
333void* CacheFileSystem::getObject(const Guid& guid)
334{
335    if (guid == CacheFileSystem::getTypeGuid())
336    {
337        return this;
338    }
339    return nullptr;
340}
341
342CacheFileSystem::CacheFileSystem(
343    ISlangFileSystem* fileSystem,
344    UniqueIdentityMode uniqueIdentityMode,
345    PathStyle pathStyle)
346{
347    setInnerFileSystem(fileSystem, uniqueIdentityMode, pathStyle);
348}
349
350CacheFileSystem::~CacheFileSystem()
351{
352    for (const auto& [_, pathInfo] : m_uniqueIdentityMap)
353        delete pathInfo;
354}
355
356void CacheFileSystem::setInnerFileSystem(
357    ISlangFileSystem* fileSystem,
358    UniqueIdentityMode uniqueIdentityMode,
359    PathStyle pathStyle)
360{
361    m_fileSystem = fileSystem;
362
363    m_uniqueIdentityMode = uniqueIdentityMode;
364    m_pathStyle = pathStyle;
365
366    m_fileSystemExt.setNull();
367
368    if (fileSystem)
369    {
370        // Try to get the more sophisticated interface
371        fileSystem->queryInterface(SLANG_IID_PPV_ARGS(m_fileSystemExt.writeRef()));
372    }
373
374    // Determine how paths map
375    m_osPathKind = m_fileSystemExt ? m_fileSystemExt->getOSPathKind() : OSPathKind::None;
376
377    switch (m_uniqueIdentityMode)
378    {
379    case UniqueIdentityMode::Default:
380    case UniqueIdentityMode::FileSystemExt:
381        {
382            // If it's not a complete file system, we will default to SimplifyAndHash style by
383            // default
384            m_uniqueIdentityMode = m_fileSystemExt ? UniqueIdentityMode::FileSystemExt
385                                                   : UniqueIdentityMode::SimplifyPathAndHash;
386            break;
387        }
388    default:
389        break;
390    }
391
392    if (pathStyle == PathStyle::Default)
393    {
394        // We'll assume it's simplify-able
395        m_pathStyle = PathStyle::Simplifiable;
396        // If we have fileSystemExt, we defer to that
397        if (m_fileSystemExt)
398        {
399            // We just defer to the m_fileSystem
400            m_pathStyle = PathStyle::FileSystemExt;
401        }
402    }
403
404    // It can't be default
405    SLANG_ASSERT(m_uniqueIdentityMode != UniqueIdentityMode::Default);
406}
407
408void CacheFileSystem::clearCache()
409{
410    for (const auto& [_, pathInfo] : m_uniqueIdentityMap)
411        delete pathInfo;
412
413    m_uniqueIdentityMap.clear();
414    m_pathMap.clear();
415
416    if (m_fileSystemExt)
417    {
418        m_fileSystemExt->clearCache();
419    }
420}
421
422
423// Determines if we can simplify a path for a given mode
424static bool _canSimplifyPath(CacheFileSystem::UniqueIdentityMode mode)
425{
426    typedef CacheFileSystem::UniqueIdentityMode UniqueIdentityMode;
427    switch (mode)
428    {
429    case UniqueIdentityMode::SimplifyPath:
430    case UniqueIdentityMode::SimplifyPathAndHash:
431        {
432            return true;
433        }
434    default:
435        {
436            return false;
437        }
438    }
439}
440
441SlangResult CacheFileSystem::enumeratePathContents(
442    const char* path,
443    FileSystemContentsCallBack callback,
444    void* userData)
445{
446    if (m_fileSystemExt)
447    {
448        return m_fileSystemExt->enumeratePathContents(path, callback, userData);
449    }
450
451    // Okay.. the contents of the 'cache' *is* the filesystem. So lets iterate over that
452    // This will win no prizes for efficiency, but that is unlikely to matter for typical usage
453
454    if (!_canSimplifyPath(m_uniqueIdentityMode))
455    {
456        // As it stands if we can't simplify paths, it's kind of hard to make this
457        // all work. As we use the simplified path cache
458        return SLANG_E_NOT_IMPLEMENTED;
459    }
460
461    // Simplify the path
462    String simplifiedPath = Path::simplify(path);
463
464    // If the simplified path is just a . then we don't have any prefix
465    if (simplifiedPath == ".")
466    {
467        simplifiedPath = "";
468    }
469
470    for (const auto& [currentPath, pathInfo] : m_pathMap)
471    {
472        // NOTE! The currentPath can be a *non* simplified path (the m_pathMap is the cache of paths
473        // simplified and other to a file/directory) Also note that there will always be the
474        // simplified version of the path in cache.
475
476        // If it doesn't start with simplified path, then it can't be a hit
477        if (!currentPath.startsWith(simplifiedPath))
478        {
479            continue;
480        }
481
482        UnownedStringSlice remaining(
483            currentPath.getBuffer() + simplifiedPath.getLength(),
484            currentPath.end());
485
486        // If it starts with a / delimiter strip it
487        if (remaining.getLength() > 0 && remaining[0] == '/')
488        {
489            remaining = UnownedStringSlice(remaining.begin() + 1, remaining.end());
490        }
491
492        // If it has a path separator then it's either not simplified - so we ignore (we only want
493        // to invoke on the simplified path version as there is only one of these for every
494        // PathInfo) or it is a child file/directory, and so we ignore that too.
495        if (remaining.indexOf('/') >= 0 || remaining.indexOf('\\') >= 0)
496        {
497            continue;
498        }
499
500        // We *know* that remaining comes from the end of currentPath .We also know currentPath is
501        // zero terminated. So we can just use (normally this would be a problem because
502        // UnownedStringSlice is generally *not* followed by zero termination.
503        const char* foundPath = remaining.begin();
504        // Let's check that fact...
505        SLANG_ASSERT(foundPath[remaining.getLength()] == 0);
506
507        SlangPathType pathType;
508        if (SLANG_FAILED(_getPathType(pathInfo, currentPath.getBuffer(), &pathType)))
509        {
510            continue;
511        }
512
513        callback(pathType, foundPath, userData);
514    }
515
516    return SLANG_OK;
517}
518
519
520SlangResult CacheFileSystem::_calcUniqueIdentity(
521    const String& path,
522    String& outUniqueIdentity,
523    ComPtr<ISlangBlob>& outFileContents)
524{
525    switch (m_uniqueIdentityMode)
526    {
527    case UniqueIdentityMode::FileSystemExt:
528        {
529            // Try getting the uniqueIdentity by asking underlying file system
530            ComPtr<ISlangBlob> uniqueIdentity;
531            SLANG_RETURN_ON_FAIL(m_fileSystemExt->getFileUniqueIdentity(
532                path.getBuffer(),
533                uniqueIdentity.writeRef()));
534            // Get the path as a string
535            outUniqueIdentity = StringUtil::getString(uniqueIdentity);
536            return SLANG_OK;
537        }
538    case UniqueIdentityMode::Path:
539        {
540            outUniqueIdentity = path;
541            return SLANG_OK;
542        }
543    case UniqueIdentityMode::SimplifyPath:
544        {
545            outUniqueIdentity = Path::simplify(path);
546            // If it still has relative elements can't uniquely identify, so give up
547            return Path::hasRelativeElement(outUniqueIdentity) ? SLANG_FAIL : SLANG_OK;
548        }
549    case UniqueIdentityMode::SimplifyPathAndHash:
550    case UniqueIdentityMode::Hash:
551        {
552            // If m_uniqueIdentityMode is SimplifyPathAndHash, the path will already be simplified
553            // before this function is hit (and it hasn't been found via path lookup). That being
554            // the case only option left is to 'hash' (or fallback to backing impls uniqueIdentity
555            // impl)
556
557            // If we don't have a file system -> assume cannot be found
558            if (m_fileSystem == nullptr)
559            {
560                return SLANG_E_NOT_FOUND;
561            }
562
563            // First attempt to load as a file
564            Result res = m_fileSystem->loadFile(path.getBuffer(), outFileContents.writeRef());
565
566            // If it succeeded but there is no contents, then make the result NOT_FOUND
567            res = (SLANG_SUCCEEDED(res) && outFileContents == nullptr) ? SLANG_E_NOT_FOUND : res;
568
569            // If that failed, we may be able to do something if m_fileSystemExt is available
570            if (SLANG_FAILED(res))
571            {
572                // If we have m_fileSystemExt interface we can just use it's implementation, as a
573                // fallback. Doing so will mean the uniqueIdentity will work if say it's a directory
574                if (m_fileSystemExt)
575                {
576                    ComPtr<ISlangBlob> uniqueIdentity;
577                    SLANG_RETURN_ON_FAIL(m_fileSystemExt->getFileUniqueIdentity(
578                        path.getBuffer(),
579                        uniqueIdentity.writeRef()));
580                    // Get the path as a string
581                    outUniqueIdentity = StringUtil::getString(uniqueIdentity);
582                    return SLANG_OK;
583                }
584
585                // If we can't access as a file (or use the backing implementations impl), we are in
586                // a tricky situation. The ISlangFileSystem interface provides no way to determine
587                // if the path is a directory for example - so there is no way of determining if
588                // something along the path exists.
589                //
590                // So we just return the error.
591                return res;
592            }
593
594            // Calculate the hash on the contents
595            const StableHashCode64 hash = getStableHashCode64(
596                (const char*)outFileContents->getBufferPointer(),
597                outFileContents->getBufferSize());
598
599            String hashString = Path::getFileName(path);
600            hashString = hashString.toLower();
601
602            hashString.append(':');
603
604            // The uniqueIdentity is a combination of name and hash
605            hashString.append(hash);
606
607            outUniqueIdentity = hashString;
608            return SLANG_OK;
609        }
610    }
611
612    return SLANG_FAIL;
613}
614
615CacheFileSystem::PathInfo* CacheFileSystem::_resolveUniqueIdentityCacheInfo(const String& path)
616{
617    // Use the path to produce uniqueIdentity information
618    ComPtr<ISlangBlob> fileContents;
619    String uniqueIdentity;
620
621    SlangResult res = _calcUniqueIdentity(path, uniqueIdentity, fileContents);
622    if (SLANG_FAILED(res))
623    {
624        // Was not able to create a uniqueIdentity - return failure as nullptr
625        return nullptr;
626    }
627
628    // Now try looking up by uniqueIdentity path. If not found, add a new result
629    PathInfo* pathInfo = nullptr;
630    if (!m_uniqueIdentityMap.tryGetValue(uniqueIdentity, pathInfo))
631    {
632        // Create with found uniqueIdentity
633        pathInfo = new PathInfo(uniqueIdentity);
634        m_uniqueIdentityMap.add(uniqueIdentity, pathInfo);
635    }
636
637    // At this point they must have same uniqueIdentity
638    SLANG_ASSERT(pathInfo->getUniqueIdentity() == uniqueIdentity);
639
640    // If we have the file contents (because of calc-ing uniqueIdentity), and there isn't a read
641    // file blob already store the data as if read, so doesn't get read again
642    if (fileContents && !pathInfo->m_fileBlob)
643    {
644        pathInfo->m_fileBlob = fileContents;
645        pathInfo->m_loadFileResult = CompressedResult::Ok;
646    }
647
648    return pathInfo;
649}
650
651CacheFileSystem::PathInfo* CacheFileSystem::_resolveSimplifiedPathCacheInfo(const String& path)
652{
653    // If we can simplify the path, try looking up in path cache with simplified path (as long as
654    // it's different!)
655    if (_canSimplifyPath(m_uniqueIdentityMode))
656    {
657        const String simplifiedPath = Path::simplify(path);
658        // Only lookup if the path is different - because otherwise will recurse forever...
659        if (simplifiedPath != path)
660        {
661            // This is a recursive call - and will ensure the simplified path is added to the cache
662            return _resolvePathCacheInfo(simplifiedPath);
663        }
664    }
665
666    return _resolveUniqueIdentityCacheInfo(path);
667}
668
669CacheFileSystem::PathInfo* CacheFileSystem::_resolvePathCacheInfo(const String& path)
670{
671    // Lookup in path cache
672    PathInfo* pathInfo;
673    if (m_pathMap.tryGetValue(path, pathInfo))
674    {
675        // Found so done
676        return pathInfo;
677    }
678
679    // Try getting or creating taking into account possible path simplification
680    pathInfo = _resolveSimplifiedPathCacheInfo(path);
681    // Always add the result to the path cache (even if null)
682    m_pathMap.add(path, pathInfo);
683    return pathInfo;
684}
685
686SlangResult CacheFileSystem::loadFile(char const* pathIn, ISlangBlob** blobOut)
687{
688    *blobOut = nullptr;
689    String path(pathIn);
690    PathInfo* info = _resolvePathCacheInfo(path);
691    if (!info)
692    {
693        return SLANG_FAIL;
694    }
695
696    if (info->m_loadFileResult == CompressedResult::Uninitialized)
697    {
698        info->m_loadFileResult = toCompressedResult(
699            m_fileSystem->loadFile(path.getBuffer(), info->m_fileBlob.writeRef()));
700    }
701
702    *blobOut = info->m_fileBlob;
703    if (*blobOut)
704    {
705        (*blobOut)->addRef();
706    }
707    return toResult(info->m_loadFileResult);
708}
709
710SlangResult CacheFileSystem::getFileUniqueIdentity(const char* path, ISlangBlob** outUniqueIdentity)
711{
712    *outUniqueIdentity = nullptr;
713    PathInfo* info = _resolvePathCacheInfo(path);
714    if (!info || info->m_uniqueIdentity.getLength() <= 0)
715    {
716        return SLANG_E_NOT_FOUND;
717    }
718
719    *outUniqueIdentity = StringBlob::create(info->m_uniqueIdentity).detach();
720    return SLANG_OK;
721}
722
723SlangResult CacheFileSystem::calcCombinedPath(
724    SlangPathType fromPathType,
725    const char* fromPath,
726    const char* path,
727    ISlangBlob** pathOut)
728{
729    // Just defer to contained implementation
730    switch (m_pathStyle)
731    {
732    case PathStyle::FileSystemExt:
733        {
734            return m_fileSystemExt->calcCombinedPath(fromPathType, fromPath, path, pathOut);
735        }
736    default:
737        {
738            // Just use the default implementation
739            return _calcCombinedPath(fromPathType, fromPath, path, pathOut);
740        }
741    }
742}
743
744SlangResult CacheFileSystem::_getPathType(
745    PathInfo* info,
746    const char* inPath,
747    SlangPathType* outPathType)
748{
749    if (info->m_getPathTypeResult == CompressedResult::Uninitialized)
750    {
751        if (m_fileSystemExt)
752        {
753            info->m_getPathTypeResult =
754                toCompressedResult(m_fileSystemExt->getPathType(inPath, &info->m_pathType));
755        }
756        else
757        {
758            // Okay try to load the file
759            if (info->m_loadFileResult == CompressedResult::Uninitialized)
760            {
761                info->m_loadFileResult =
762                    toCompressedResult(m_fileSystem->loadFile(inPath, info->m_fileBlob.writeRef()));
763            }
764
765            // Make the getPathResult the same as the load result
766            info->m_getPathTypeResult = info->m_loadFileResult;
767            // Just set to file... the result is what matters in this case
768            info->m_pathType = SLANG_PATH_TYPE_FILE;
769        }
770    }
771
772    *outPathType = info->m_pathType;
773    return toResult(info->m_getPathTypeResult);
774}
775
776SlangResult CacheFileSystem::getPathType(const char* inPath, SlangPathType* outPathType)
777{
778    PathInfo* info = _resolvePathCacheInfo(inPath);
779    if (!info)
780    {
781        return SLANG_E_NOT_FOUND;
782    }
783
784    return _getPathType(info, inPath, outPathType);
785}
786
787SlangResult CacheFileSystem::getPath(PathKind kind, const char* path, ISlangBlob** outPath)
788{
789    switch (kind)
790    {
791    case PathKind::Simplified:
792        return _getSimplifiedPath(path, outPath);
793    case PathKind::Canonical:
794        return _getCanonicalPath(path, outPath);
795    default:
796        break;
797    }
798
799    if (m_fileSystemExt)
800    {
801        return m_fileSystemExt->getPath(kind, path, outPath);
802    }
803
804    // If we don't have a fileSystem, we can try the canonical path
805    if (SLANG_SUCCEEDED(getPath(PathKind::Canonical, path, outPath)))
806    {
807        return SLANG_OK;
808    }
809    // Else we can try simplified
810    return getPath(PathKind::Simplified, path, outPath);
811}
812
813SlangResult CacheFileSystem::_getSimplifiedPath(const char* path, ISlangBlob** outSimplifiedPath)
814{
815    // If we have a ISlangFileSystemExt we can just pass on the request to it
816    switch (m_pathStyle)
817    {
818    case PathStyle::FileSystemExt:
819        {
820            return m_fileSystemExt->getPath(PathKind::Simplified, path, outSimplifiedPath);
821        }
822    case PathStyle::Simplifiable:
823        {
824            String simplifiedPath = Path::simplify(path);
825            *outSimplifiedPath = StringUtil::createStringBlob(simplifiedPath).detach();
826            return SLANG_OK;
827        }
828    default:
829        return SLANG_E_NOT_IMPLEMENTED;
830    }
831}
832
833SlangResult CacheFileSystem::_getCanonicalPath(const char* path, ISlangBlob** outCanonicalPath)
834{
835    *outCanonicalPath = nullptr;
836
837    // A file must exist to get a canonical path...
838    PathInfo* info = _resolvePathCacheInfo(path);
839    if (!info)
840    {
841        return SLANG_E_NOT_FOUND;
842    }
843
844    // We don't have this -> so read it ...
845    if (info->m_getCanonicalPathResult == CompressedResult::Uninitialized)
846    {
847        if (!m_fileSystemExt)
848        {
849            return SLANG_E_NOT_IMPLEMENTED;
850        }
851
852        // Try getting the canonicalPath by asking underlying file system
853        ComPtr<ISlangBlob> canonicalPathBlob;
854        SlangResult res =
855            m_fileSystemExt->getPath(PathKind::Canonical, path, canonicalPathBlob.writeRef());
856
857        if (SLANG_SUCCEEDED(res))
858        {
859            // Get the path as a string
860            info->m_canonicalPath = StringUtil::getString(canonicalPathBlob);
861            if (info->m_canonicalPath.getLength() <= 0)
862            {
863                res = SLANG_FAIL;
864            }
865        }
866
867        // Save the result
868        info->m_getCanonicalPathResult = toCompressedResult(res);
869    }
870
871    // Create the blob
872    if (info->m_canonicalPath.getLength())
873    {
874        *outCanonicalPath = StringBlob::create(info->m_canonicalPath).detach();
875    }
876
877    return SLANG_OK;
878}
879
880/* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!  RelativeFileSystem  !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! */
881
882RelativeFileSystem::RelativeFileSystem(
883    ISlangFileSystem* fileSystem,
884    const String& relativePath,
885    bool stripPath)
886    : m_relativePath(relativePath), m_stripPath(stripPath)
887{
888    m_style = _getFileSystemStyle(fileSystem, m_fileSystem);
889
890    m_osPathKind = OSPathKind::None;
891
892    ComPtr<ISlangFileSystemExt> ext;
893    if (SLANG_SUCCEEDED(fileSystem->queryInterface(SLANG_IID_PPV_ARGS(ext.writeRef()))))
894    {
895        m_osPathKind = ext->getOSPathKind();
896
897        // If it's direct, but we have a relative path, "operating system" should work
898        if (m_osPathKind == OSPathKind::Direct && relativePath.getLength())
899        {
900            m_osPathKind = OSPathKind::OperatingSystem;
901        }
902    }
903}
904
905ISlangUnknown* RelativeFileSystem::getInterface(const Guid& guid)
906{
907    return _canCast(m_style, guid) ? static_cast<ISlangMutableFileSystem*>(this) : nullptr;
908}
909
910void* RelativeFileSystem::getObject(const Guid& guid)
911{
912    SLANG_UNUSED(guid);
913    return nullptr;
914}
915
916void* RelativeFileSystem::castAs(const Guid& guid)
917{
918    if (auto ptr = getInterface(guid))
919    {
920        return ptr;
921    }
922    return getObject(guid);
923}
924
925SlangResult RelativeFileSystem::_calcCombinedPathInner(
926    SlangPathType fromPathType,
927    const char* fromPath,
928    const char* path,
929    ISlangBlob** outPath)
930{
931    ISlangFileSystemExt* fileSystem = _getExt();
932    if (fileSystem)
933    {
934        return fileSystem->calcCombinedPath(fromPathType, fromPath, path, outPath);
935    }
936    else
937    {
938        return _calcCombinedPath(fromPathType, fromPath, path, outPath);
939    }
940}
941
942SlangResult RelativeFileSystem::_getCanonicalPath(const char* path, String& outPath)
943{
944    if (m_stripPath)
945    {
946        // We are just using the filename. There is no path that could go outside of the the
947        // relative path so we can use as is
948        outPath = Path::getFileName(path);
949    }
950    else
951    {
952        // NOTE that we don't want the canonical path to be absolute with a leading "/"
953        // because paths specified which aren't absolute, would produce a different path.
954        //
955        // Ie we want (and get with these options)
956        // "a" -> "a"
957        // "/a" -> "a".
958        //
959        // If we allowed the root to be included then...
960        // "a" -> "a"
961        // "/a" -> "/a"
962        //
963        // Two identical paths would match to different paths, which wouldn't be canonical.
964        //
965        // This could be fixed by making all paths absolute with '/' too, but it's easier to just
966        // make all not have "/"
967
968        StringBuilder canonicalPath;
969        // We want the input path to be local to this file system
970        SLANG_RETURN_ON_FAIL(
971            Path::simplify(path, Path::SimplifyStyle::AbsoluteOnlyAndNoRoot, canonicalPath));
972        outPath = canonicalPath;
973    }
974    return SLANG_OK;
975}
976
977SlangResult RelativeFileSystem::_getFixedPath(const char* path, String& outPath)
978{
979    ComPtr<ISlangBlob> blob;
980
981    String canonicalPath;
982    SLANG_RETURN_ON_FAIL(_getCanonicalPath(path, canonicalPath));
983
984    SLANG_RETURN_ON_FAIL(_calcCombinedPathInner(
985        SLANG_PATH_TYPE_DIRECTORY,
986        m_relativePath.getBuffer(),
987        canonicalPath.getBuffer(),
988        blob.writeRef()));
989    outPath = StringUtil::getString(blob);
990
991    return SLANG_OK;
992}
993
994SlangResult RelativeFileSystem::loadFile(char const* path, ISlangBlob** outBlob)
995{
996    String fixedPath;
997    SLANG_RETURN_ON_FAIL(_getFixedPath(path, fixedPath));
998    return m_fileSystem->loadFile(fixedPath.getBuffer(), outBlob);
999}
1000
1001SlangResult RelativeFileSystem::getFileUniqueIdentity(
1002    const char* path,
1003    ISlangBlob** outUniqueIdentity)
1004{
1005    auto fileSystem = _getExt();
1006    if (!fileSystem)
1007        return SLANG_E_NOT_IMPLEMENTED;
1008
1009    String fixedPath;
1010    SLANG_RETURN_ON_FAIL(_getFixedPath(path, fixedPath));
1011    return fileSystem->getFileUniqueIdentity(fixedPath.getBuffer(), outUniqueIdentity);
1012}
1013
1014SlangResult RelativeFileSystem::calcCombinedPath(
1015    SlangPathType fromPathType,
1016    const char* fromPath,
1017    const char* path,
1018    ISlangBlob** outPath)
1019{
1020    auto fileSystem = _getExt();
1021    if (!fileSystem)
1022        return SLANG_E_NOT_IMPLEMENTED;
1023
1024    String fixedFromPath;
1025    SLANG_RETURN_ON_FAIL(_getFixedPath(fromPath, fixedFromPath));
1026
1027    return fileSystem->calcCombinedPath(fromPathType, fixedFromPath.getBuffer(), path, outPath);
1028}
1029
1030SlangResult RelativeFileSystem::getPathType(const char* path, SlangPathType* outPathType)
1031{
1032    auto fileSystem = _getExt();
1033    if (!fileSystem)
1034        return SLANG_E_NOT_IMPLEMENTED;
1035
1036    String fixedPath;
1037    SLANG_RETURN_ON_FAIL(_getFixedPath(path, fixedPath));
1038    return fileSystem->getPathType(fixedPath.getBuffer(), outPathType);
1039}
1040
1041SlangResult RelativeFileSystem::getPath(PathKind kind, const char* path, ISlangBlob** outPath)
1042{
1043    auto fileSystem = _getExt();
1044    if (!fileSystem)
1045        return SLANG_E_NOT_IMPLEMENTED;
1046
1047    switch (kind)
1048    {
1049    case PathKind::Simplified:
1050        {
1051            return fileSystem->getPath(kind, path, outPath);
1052        }
1053    case PathKind::Display:
1054        {
1055            // If not backed by OS, just use simplified path, else use the Operating system path
1056            kind = (fileSystem->getOSPathKind() == OSPathKind::None) ? PathKind::Simplified
1057                                                                     : PathKind::OperatingSystem;
1058            return getPath(kind, path, outPath);
1059        }
1060    case PathKind::Canonical:
1061        {
1062            String canonicalPath;
1063            SLANG_RETURN_ON_FAIL(_getCanonicalPath(path, canonicalPath));
1064            *outPath = StringBlob::moveCreate(canonicalPath).detach();
1065            return SLANG_OK;
1066        }
1067    case PathKind::OperatingSystem:
1068        {
1069            String fixedPath;
1070            SLANG_RETURN_ON_FAIL(_getFixedPath(path, fixedPath));
1071            return fileSystem->getPath(kind, fixedPath.getBuffer(), outPath);
1072        }
1073    }
1074
1075    return SLANG_FAIL;
1076}
1077
1078void RelativeFileSystem::clearCache()
1079{
1080    auto fileSystem = _getExt();
1081    if (!fileSystem)
1082        return;
1083
1084    fileSystem->clearCache();
1085}
1086
1087SlangResult RelativeFileSystem::enumeratePathContents(
1088    const char* path,
1089    FileSystemContentsCallBack callback,
1090    void* userData)
1091{
1092    auto fileSystem = _getExt();
1093    if (!fileSystem)
1094        return SLANG_E_NOT_IMPLEMENTED;
1095
1096    String fixedPath;
1097    SLANG_RETURN_ON_FAIL(_getFixedPath(path, fixedPath));
1098    return fileSystem->enumeratePathContents(fixedPath.getBuffer(), callback, userData);
1099}
1100
1101SlangResult RelativeFileSystem::saveFile(const char* path, const void* data, size_t size)
1102{
1103    auto fileSystem = _getMutable();
1104    if (!fileSystem)
1105        return SLANG_E_NOT_IMPLEMENTED;
1106
1107    String fixedPath;
1108    SLANG_RETURN_ON_FAIL(_getFixedPath(path, fixedPath));
1109    return fileSystem->saveFile(fixedPath.getBuffer(), data, size);
1110}
1111
1112SlangResult RelativeFileSystem::saveFileBlob(const char* path, ISlangBlob* dataBlob)
1113{
1114    auto fileSystem = _getMutable();
1115    if (!fileSystem)
1116        return SLANG_E_NOT_IMPLEMENTED;
1117
1118    String fixedPath;
1119    SLANG_RETURN_ON_FAIL(_getFixedPath(path, fixedPath));
1120    return fileSystem->saveFileBlob(fixedPath.getBuffer(), dataBlob);
1121}
1122
1123SlangResult RelativeFileSystem::remove(const char* path)
1124{
1125    auto fileSystem = _getMutable();
1126    if (!fileSystem)
1127        return SLANG_E_NOT_IMPLEMENTED;
1128
1129    String fixedPath;
1130    SLANG_RETURN_ON_FAIL(_getFixedPath(path, fixedPath));
1131    return fileSystem->remove(fixedPath.getBuffer());
1132}
1133
1134SlangResult RelativeFileSystem::createDirectory(const char* path)
1135{
1136    auto fileSystem = _getMutable();
1137    if (!fileSystem)
1138        return SLANG_E_NOT_IMPLEMENTED;
1139
1140    String fixedPath;
1141    SLANG_RETURN_ON_FAIL(_getFixedPath(path, fixedPath));
1142    return fileSystem->createDirectory(fixedPath.getBuffer());
1143}
1144
1145} // namespace Slang