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