yum-mirror/slang

Making it easier to work with shaders

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

Jay KwakFix the intermittent failures of tests/autodiff/auto-differential-type (#7006)0f4a32fcc

master
17.7 KiB768 linesraw
1#include "slang-stream.h"
2#ifdef _WIN32
3#include <share.h>
4#endif
5#include "slang-io.h"
6#include "slang-process.h"
7
8#include <stdio.h>
9#include <thread>
10
11namespace Slang
12{
13
14// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! FileStream !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
15
16SlangResult Stream::readExactly(void* buffer, size_t length)
17{
18    size_t readBytes;
19    SLANG_RETURN_ON_FAIL(read(buffer, length, readBytes));
20    return (readBytes == length) ? SLANG_OK : SLANG_FAIL;
21}
22
23// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! FileStream !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
24
25FileStream::FileStream()
26    : m_handle(nullptr), m_fileAccess(FileAccess::None), m_endReached(false)
27{
28}
29
30SlangResult FileStream::init(const String& fileName, FileMode fileMode)
31{
32    const FileAccess access = (fileMode == FileMode::Open) ? FileAccess::Read : FileAccess::Write;
33    return _init(fileName, fileMode, access, FileShare::None);
34}
35
36SlangResult FileStream::init(
37    const String& fileName,
38    FileMode fileMode,
39    FileAccess access,
40    FileShare share)
41{
42    return _init(fileName, fileMode, access, share);
43}
44
45SlangResult FileStream::_init(
46    const String& fileName,
47    FileMode fileMode,
48    FileAccess access,
49    // Only used on Windows
50    [[maybe_unused]] FileShare share)
51{
52    // Make sure it's closed to start with
53    close();
54
55    if (access == FileAccess::None)
56    {
57        SLANG_ASSERT(!"FileAccess::None not valid to create a FileStream.");
58        return SLANG_E_INVALID_ARG;
59    }
60
61    const char* mode = "rt";
62    switch (fileMode)
63    {
64    case FileMode::Create:
65        if (access == FileAccess::Read)
66        {
67            SLANG_ASSERT(!"Read-only access is incompatible with Create mode.");
68            return SLANG_E_INVALID_ARG;
69        }
70        else if (access == FileAccess::ReadWrite)
71        {
72            mode = "w+b";
73        }
74        else
75        {
76            mode = "wb";
77        }
78        break;
79    case FileMode::Open:
80        if (access == FileAccess::Read)
81        {
82            mode = "rb";
83        }
84        else if (access == FileAccess::ReadWrite)
85        {
86            mode = "r+b";
87        }
88        else
89        {
90            mode = "wb";
91        }
92        break;
93    case FileMode::CreateNew:
94        if (File::exists(fileName))
95        {
96            return SLANG_E_CANNOT_OPEN;
97        }
98        if (access == FileAccess::Read)
99        {
100            SLANG_ASSERT(!"Read-only access is incompatible with Create mode.");
101            return SLANG_E_INVALID_ARG;
102        }
103        else if (access == FileAccess::ReadWrite)
104        {
105            mode = "w+b";
106        }
107        else
108        {
109            mode = "wb";
110        }
111        break;
112    case FileMode::Append:
113        if (access == FileAccess::Read)
114        {
115            SLANG_ASSERT(!"Read-only access is incompatible with Append mode.");
116            return SLANG_E_INVALID_ARG;
117        }
118        else if (access == FileAccess::ReadWrite)
119        {
120            mode = "a+b";
121        }
122        else
123        {
124            mode = "ab";
125        }
126        break;
127    default:
128        break;
129    }
130#ifdef _WIN32
131
132    // NOTE! This works because we know all the characters in the mode
133    // are encoded directly as the same value in a wchar_t.
134    //
135    // Work out the length *including* terminating 0
136    const Index modeLength = Index(::strlen(mode)) + 1;
137    wchar_t wideMode[8];
138    SLANG_ASSERT(modeLength <= SLANG_COUNT_OF(wideMode));
139
140    // Copy to wchar_t
141    for (Index i = 0; i < modeLength; ++i)
142    {
143        wideMode[i] = wchar_t(mode[i]);
144    }
145
146    int shFlag = _SH_DENYRW;
147    switch (share)
148    {
149    case FileShare::None:
150        shFlag = _SH_DENYRW;
151        break;
152    case FileShare::ReadOnly:
153        shFlag = _SH_DENYWR;
154        break;
155    case FileShare::WriteOnly:
156        shFlag = _SH_DENYRD;
157        break;
158    case FileShare::ReadWrite:
159        shFlag = _SH_DENYNO;
160        break;
161    default:
162        SLANG_ASSERT(!"Invalid file share mode.");
163        return SLANG_FAIL;
164    }
165
166    if (share == FileShare::None)
167    {
168        m_handle = _wfsopen(fileName.toWString(), wideMode, _SH_DENYNO);
169    }
170    else
171    {
172        m_handle = _wfsopen(fileName.toWString(), wideMode, shFlag);
173    }
174#else
175    m_handle = fopen(fileName.getBuffer(), mode);
176#endif
177    if (!m_handle)
178    {
179        return SLANG_E_CANNOT_OPEN;
180    }
181
182    // Just set the access specified
183    m_fileAccess = access;
184    return SLANG_OK;
185}
186
187FileStream::~FileStream()
188{
189    close();
190}
191
192Int64 FileStream::getPosition()
193{
194#if defined(_WIN32) || defined(__CYGWIN__)
195    fpos_t pos;
196    fgetpos(m_handle, &pos);
197    return pos;
198#elif defined(__APPLE__)
199    return ftell(m_handle);
200#else
201    fpos64_t pos;
202    fgetpos64(m_handle, &pos);
203    return *(Int64*)(&pos);
204#endif
205}
206
207SlangResult FileStream::seek(SeekOrigin seekOrigin, Int64 offset)
208{
209    int fseekOrigin;
210    switch (seekOrigin)
211    {
212    case SeekOrigin::Start:
213        fseekOrigin = SEEK_SET;
214        break;
215    case SeekOrigin::End:
216        fseekOrigin = SEEK_END;
217        break;
218    case SeekOrigin::Current:
219        fseekOrigin = SEEK_CUR;
220        break;
221    default:
222        SLANG_ASSERT(!"Unsupported seek origin.");
223        return SLANG_FAIL;
224    }
225
226    // If endReached is intended to be like feof - then doing a seek will reset it
227    m_endReached = false;
228
229#ifdef _WIN32
230    int rs = _fseeki64(m_handle, offset, fseekOrigin);
231#else
232    int rs = fseek(m_handle, (long int)offset, fseekOrigin);
233#endif
234
235    // If rs != 0 then the the seek failed
236    SLANG_ASSERT(rs == 0);
237
238    return (rs == 0) ? SLANG_OK : SLANG_FAIL;
239}
240
241SlangResult FileStream::read(void* buffer, size_t length, size_t& outBytesRead)
242{
243    auto bytesRead = fread_s(buffer, length, 1, length, m_handle);
244
245    outBytesRead = bytesRead;
246    if (bytesRead == 0 && length > 0)
247    {
248        // If we have reached the end, then reading nothing is ok.
249        if (!m_endReached)
250        {
251            // If we are not at the end of the file we should be able to read some bytes
252            if (!feof(m_handle))
253            {
254                return SLANG_FAIL;
255            }
256            m_endReached = true;
257        }
258    }
259    return SLANG_OK;
260}
261
262SlangResult FileStream::write(const void* buffer, size_t length)
263{
264    auto bytesWritten = fwrite(buffer, 1, length, m_handle);
265    return (bytesWritten == length) ? SLANG_OK : SLANG_FAIL;
266}
267
268SlangResult FileStream::flush()
269{
270    if (m_handle && canWrite())
271    {
272        fflush(m_handle);
273        return SLANG_OK;
274    }
275    return SLANG_E_NOT_AVAILABLE;
276}
277
278bool FileStream::canRead()
279{
280    return ((int)m_fileAccess & (int)FileAccess::Read) != 0;
281}
282
283bool FileStream::canWrite()
284{
285    return ((int)m_fileAccess & (int)FileAccess::Write) != 0;
286}
287
288void FileStream::close()
289{
290    if (m_handle)
291    {
292        fclose(m_handle);
293        m_handle = nullptr;
294
295        // If closed, can neither read or write
296        m_fileAccess = FileAccess::None;
297    }
298}
299
300bool FileStream::isEnd()
301{
302    return m_endReached;
303}
304
305// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!! MemoryStreamBase !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
306
307SlangResult MemoryStreamBase::seek(SeekOrigin origin, Int64 offset)
308{
309    Int64 pos = 0;
310    switch (origin)
311    {
312    case SeekOrigin::Start:
313        pos = offset;
314        break;
315    case SeekOrigin::End:
316        pos = Int64(m_contentsSize) + offset;
317        break;
318    case SeekOrigin::Current:
319        pos = Int64(m_position) + offset;
320        break;
321    default:
322        SLANG_ASSERT(!"Unsupported seek origin.");
323        return SLANG_E_NOT_IMPLEMENTED;
324    }
325
326    m_atEnd = false;
327
328    // Clamp to the valid range
329    pos = (pos < 0) ? 0 : pos;
330    pos = (pos > Int64(m_contentsSize)) ? Int64(m_contentsSize) : pos;
331
332    m_position = ptrdiff_t(pos);
333    return SLANG_OK;
334}
335
336SlangResult MemoryStreamBase::read(void* buffer, size_t length, size_t& outReadBytes)
337{
338    outReadBytes = 0;
339    if (!canRead())
340    {
341        SLANG_ASSERT(!"Cannot read this stream.");
342        return SLANG_FAIL;
343    }
344
345    const size_t maxRead = size_t(m_contentsSize - m_position);
346    if (maxRead == 0 && length > 0)
347    {
348        // At end of stream
349        m_atEnd = true;
350        return SLANG_OK;
351    }
352
353    length = length > maxRead ? maxRead : length;
354
355    ::memcpy(buffer, m_contents + m_position, length);
356    m_position += ptrdiff_t(length);
357    outReadBytes = length;
358
359    return SLANG_OK;
360}
361
362// !!!!!!!!!!!!!!!!!!!!!!!!!!!!! OwnedMemoryStream !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
363
364SlangResult OwnedMemoryStream::write(const void* buffer, size_t length)
365{
366    if (!canWrite())
367    {
368        SLANG_ASSERT(!"Cannot write this stream.");
369        return SLANG_FAIL;
370    }
371
372    if (m_position == m_ownedContents.getCount())
373    {
374        m_ownedContents.addRange((const uint8_t*)buffer, Index(length));
375    }
376    else
377    {
378        m_ownedContents.insertRange(m_position, (const uint8_t*)buffer, Index(length));
379    }
380
381    m_contents = m_ownedContents.getBuffer();
382    m_contentsSize = ptrdiff_t(m_ownedContents.getCount());
383
384    m_atEnd = false;
385
386    m_position += ptrdiff_t(length);
387    return SLANG_OK;
388}
389
390// !!!!!!!!!!!!!!!!!!!!!!!!!!!!! BufferedReadStream !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
391
392void BufferedReadStream::consume(Index byteCount)
393{
394    SLANG_ASSERT(Index(getCount()) >= byteCount && byteCount >= 0);
395    m_startIndex += byteCount;
396    if (getCount() == 0)
397    {
398        _resetBuffer();
399    }
400}
401
402Int64 BufferedReadStream::getPosition()
403{
404    return m_stream ? (m_stream->getPosition() - getCount()) : 0;
405}
406
407SlangResult BufferedReadStream::seek(SeekOrigin origin, Int64 offset)
408{
409    if (!m_stream)
410    {
411        return SLANG_FAIL;
412    }
413    // As it currently stands the data behind m_startIndex is the previous data.
414    // So we could seek backwards up to -m_startIndex.
415    // We don't worry about this here, for simplicity sake.
416
417    if (origin == SeekOrigin::End || origin == SeekOrigin::Start || offset < 0 ||
418        offset >= Int64(getCount()))
419    {
420        // Empty the buffer
421        _resetBuffer();
422        // Seek on underlying stream
423        return m_stream->seek(origin, offset);
424    }
425
426    // We can just seek on the buffered data
427    consume(Index(offset));
428    return SLANG_OK;
429}
430
431SlangResult BufferedReadStream::read(void* inBuffer, size_t length, size_t& outReadBytes)
432{
433    // If the buffer has no data and the read size is larger than the default read size - may as
434    // well just read directly into the output buffer
435    if (getCount() == 0 && length > m_defaultReadSize)
436    {
437        return m_stream->read(inBuffer, length, outReadBytes);
438    }
439
440    Byte* buffer = (Byte*)inBuffer;
441
442    size_t totalReadBytes = 0;
443    outReadBytes = 0;
444
445    // Do a read to fill the buffer.
446    SLANG_RETURN_ON_FAIL(update());
447
448    while (length > 0)
449    {
450        const size_t bufferCount = size_t(getCount());
451
452        if (bufferCount)
453        {
454            const size_t readCount = (bufferCount < length) ? bufferCount : length;
455
456            ::memcpy(buffer, getBuffer(), readCount);
457
458            consume(Index(readCount));
459            buffer += readCount;
460            length -= readCount;
461
462            totalReadBytes += readCount;
463        }
464        else
465        {
466            if (m_stream == nullptr)
467            {
468                break;
469            }
470
471            // Read from underlying buffer
472            size_t readBytes;
473            SlangResult res = m_stream->read(buffer, length, readBytes);
474
475            outReadBytes = totalReadBytes + readBytes;
476            return res;
477        }
478    }
479
480    outReadBytes = totalReadBytes;
481    return SLANG_OK;
482}
483
484SlangResult BufferedReadStream::write(const void* buffer, size_t length)
485{
486    SLANG_UNUSED(buffer);
487    SLANG_UNUSED(length);
488
489    return SLANG_E_NOT_AVAILABLE;
490}
491
492bool BufferedReadStream::canRead()
493{
494    return getCount() > 0 || (m_stream && m_stream->canRead());
495}
496
497bool BufferedReadStream::canWrite()
498{
499    return false;
500}
501
502void BufferedReadStream::close()
503{
504    if (m_stream)
505    {
506        m_stream->close();
507        m_stream.setNull();
508    }
509}
510
511bool BufferedReadStream::isEnd()
512{
513    return getCount() == 0 && (m_stream == nullptr || m_stream->isEnd());
514}
515
516SlangResult BufferedReadStream::flush()
517{
518    return SLANG_E_NOT_AVAILABLE;
519}
520
521SlangResult BufferedReadStream::update()
522{
523    if (m_stream == nullptr)
524    {
525        // Should this return an error?
526        return SLANG_OK;
527    }
528
529    // Repeat until we have enough space
530    for (;;)
531    {
532        // How much buffer space do we have. We need at least m_defaultReadSize
533        const size_t remainingCount = size_t(m_buffer.getCapacity() - m_buffer.getCount());
534
535        if (remainingCount >= m_defaultReadSize)
536        {
537            break;
538        }
539
540        // If there is anything in the buffer shift it all down
541        if (m_startIndex > 0)
542        {
543            Byte* buffer = m_buffer.getBuffer();
544            const Index count = getCount();
545            if (count > 0)
546            {
547                ::memmove(buffer, buffer + m_startIndex, count);
548            }
549
550            m_buffer.setCount(count);
551            m_startIndex = 0;
552        }
553        else
554        {
555            // Make sure we have the space
556            const Index prevCount = m_buffer.getCount();
557            m_buffer.setCount(prevCount + m_defaultReadSize);
558            m_buffer.setCount(prevCount);
559        }
560    }
561
562    {
563        const Index prevCount = m_buffer.getCount();
564        m_buffer.setCount(prevCount + m_defaultReadSize);
565
566        size_t readBytes = 0;
567
568        const SlangResult res =
569            m_stream->read(m_buffer.getBuffer() + prevCount, m_defaultReadSize, readBytes);
570
571        m_buffer.setCount(prevCount + Index(readBytes));
572
573        return res;
574    }
575}
576
577SlangResult BufferedReadStream::readUntilContains(size_t size)
578{
579    while (true)
580    {
581        if (size_t(getCount()) >= size)
582        {
583            return SLANG_OK;
584        }
585
586        const size_t preCount = size_t(getCount());
587
588        // Update buffer
589        SLANG_RETURN_ON_FAIL(update());
590
591        // If nothing was read yield
592        if (preCount == getCount())
593        {
594            Process::sleepCurrentThread(0);
595        }
596    }
597}
598
599
600// !!!!!!!!!!!!!!!!!!!!!!!!!!!!! StreamUtil !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
601
602SlangResult StreamUtil::readAndWrite(
603    Stream* writeStream,
604    ArrayView<Byte> bytesToWrite,
605    Stream* readStream,
606    List<Byte>& outReadBytes,
607    Stream* errStream,
608    List<Byte>& outErrBytes)
609{
610    std::thread writeThread(
611        [&]()
612        {
613            writeStream->write(bytesToWrite.getBuffer(), (size_t)bytesToWrite.getCount());
614            writeStream->close();
615        });
616    SlangResult readResult = SLANG_OK;
617    std::thread readThread([&]() { readResult = readAll(readStream, 1024, outReadBytes); });
618    std::thread readErrThread([&]() { readAll(errStream, 1024, outErrBytes); });
619    writeThread.join();
620    readThread.join();
621    readErrThread.join();
622    return readResult;
623}
624
625/* static */ SlangResult StreamUtil::readAll(Stream* stream, size_t readSize, List<Byte>& ioBytes)
626{
627    while (!stream->isEnd())
628    {
629        SLANG_RETURN_ON_FAIL(read(stream, readSize, ioBytes));
630    }
631
632    return SLANG_OK;
633}
634
635/* static */ SlangResult StreamUtil::read(Stream* stream, size_t readSize, List<Byte>& ioBytes)
636{
637    readSize = (readSize <= 0) ? 1024 : readSize;
638
639    while (true)
640    {
641        const Index prevCount = ioBytes.getCount();
642        ioBytes.setCount(prevCount + readSize);
643
644        size_t readBytesCount;
645        SLANG_RETURN_ON_FAIL(
646            stream->read(ioBytes.getBuffer() + prevCount, readSize, readBytesCount));
647        ioBytes.setCount(prevCount + Index(readBytesCount));
648
649        if (readBytesCount == 0)
650        {
651            return SLANG_OK;
652        }
653    }
654}
655
656/* static */ SlangResult StreamUtil::discard(Stream* stream)
657{
658    Byte buf[1024];
659    const Index bufSize = SLANG_COUNT_OF(buf);
660
661    while (true)
662    {
663        size_t readBytesCount;
664        SLANG_RETURN_ON_FAIL(stream->read(buf, bufSize, readBytesCount));
665
666        if (readBytesCount == 0)
667        {
668            return SLANG_OK;
669        }
670    }
671}
672
673/* static */ SlangResult StreamUtil::discardAll(Stream* stream)
674{
675    while (!stream->isEnd())
676    {
677        SLANG_RETURN_ON_FAIL(discard(stream));
678    }
679    return SLANG_OK;
680}
681
682
683/* static */ SlangResult StreamUtil::readOrDiscard(
684    Stream* stream,
685    size_t readSize,
686    List<Byte>* ioBytes)
687{
688    if (ioBytes)
689    {
690        return read(stream, readSize, *ioBytes);
691    }
692    else
693    {
694        return discard(stream);
695    }
696}
697
698/* static */ SlangResult StreamUtil::readOrDiscardAll(
699    Stream* stream,
700    size_t readSize,
701    List<Byte>* ioBytes)
702{
703    if (ioBytes)
704    {
705        return readAll(stream, readSize, *ioBytes);
706    }
707    else
708    {
709        return discardAll(stream);
710    }
711}
712
713static FILE* _getFileFromStdStreamType(StdStreamType stdStream)
714{
715    switch (stdStream)
716    {
717    case StdStreamType::ErrorOut:
718        return stderr;
719    case StdStreamType::Out:
720        return stdout;
721    case StdStreamType::In:
722        return stdin;
723    default:
724        return nullptr;
725    }
726}
727
728static int _getBufferOptions(StreamBufferStyle style)
729{
730    switch (style)
731    {
732    case StreamBufferStyle::None:
733        return _IONBF;
734    case StreamBufferStyle::Line:
735        return _IOLBF;
736    default:
737    case StreamBufferStyle::Full:
738        return _IOFBF;
739    }
740}
741
742/* static */ SlangResult StreamUtil::setStreamBufferStyle(
743    StdStreamType stdStream,
744    StreamBufferStyle style)
745{
746    FILE* file = _getFileFromStdStreamType(stdStream);
747
748    if (file)
749    {
750        auto options = _getBufferOptions(style);
751
752        // https://www.cplusplus.com/reference/cstdio/setvbuf/
753
754        // NOTE! We don't set a buffer here (we pass in nullptr).
755        // Passing nullptr is fine for 'no buffering' and sets a 'dynamic buffer' for others.
756        // But it's not clear the behavior is around the buffer size. It seems the size is a
757        // 'suggestion' so it will set the default but the documentation is unclear.
758        if (setvbuf(file, nullptr, options, 0) == 0)
759        {
760            return SLANG_OK;
761        }
762        return SLANG_FAIL;
763    }
764
765    return SLANG_E_NOT_AVAILABLE;
766}
767
768} // namespace Slang