1
0
Fork 0
mirror of https://github.com/beefytech/Beef.git synced 2025-06-09 20:12:21 +02:00
Beef/BeefySysLib/sound/Win32/AkDefaultIOHookDeferred.cpp
2019-08-23 11:56:54 -07:00

404 lines
No EOL
15 KiB
C++

#include "BFPlatform.h"
#ifdef BF_WWISE_ENABLED
//////////////////////////////////////////////////////////////////////
//
// AkDefaultIOHookDeferred.cpp
//
// Default deferred low level IO hook (AK::StreamMgr::IAkIOHookDeferred)
// and file system (AK::StreamMgr::IAkFileLocationResolver) implementation
// on Windows.
//
// AK::StreamMgr::IAkFileLocationResolver:
// Resolves file location using simple path concatenation logic
// (implemented in ../Common/CAkFileLocationBase). It can be used as a
// standalone Low-Level IO system, or as part of a multi device system.
// In the latter case, you should manage multiple devices by implementing
// AK::StreamMgr::IAkFileLocationResolver elsewhere (you may take a look
// at class CAkDefaultLowLevelIODispatcher).
//
// AK::StreamMgr::IAkIOHookDeferred:
// Uses platform API for I/O. Calls to ::ReadFile() and ::WriteFile()
// do not block because files are opened with the FILE_FLAG_OVERLAPPED flag.
// Transfer completion is handled by internal FileIOCompletionRoutine function,
// which then calls the AkAIOCallback.
// The AK::StreamMgr::IAkIOHookDeferred interface is meant to be used with
// AK_SCHEDULER_DEFERRED_LINED_UP streaming devices.
//
// Init() creates a streaming device (by calling AK::StreamMgr::CreateDevice()).
// AkDeviceSettings::uSchedulerTypeFlags is set inside to AK_SCHEDULER_DEFERRED_LINED_UP.
// If there was no AK::StreamMgr::IAkFileLocationResolver previously registered
// to the Stream Manager, this object registers itself as the File Location Resolver.
//
// Copyright (c) 2006 Audiokinetic Inc. / All Rights Reserved
//
//////////////////////////////////////////////////////////////////////
#include "Common.h"
#include "AkDefaultIOHookDeferred.h"
#include <AK/SoundEngine/Common/AkMemoryMgr.h>
#include "AkFileHelpers.h"
// Device info.
#define WIN32_DEFERRED_DEVICE_NAME (L"Win32 Deferred") // Default deferred device name.
// With files opened with FILE_FLAG_NO_BUFFERING flag, accesses should be made in multiple of the
// sector size. We don't know what it is, so we choose a safe value of 2 KB.
// On Windows, real sector size can be queried with ::GetDiskFreeSpace() (but you need to know the drive letter).
#define WIN32_NO_BUFFERING_BLOCK_SIZE (2048)
AkMemPoolId CAkDefaultIOHookDeferred::m_poolID = AK_INVALID_POOL_ID;
CAkDefaultIOHookDeferred::CAkDefaultIOHookDeferred()
: m_deviceID( AK_INVALID_DEVICE_ID )
, m_bAsyncOpen( false )
{
}
CAkDefaultIOHookDeferred::~CAkDefaultIOHookDeferred()
{
}
// Initialization/termination. Init() registers this object as the one and
// only File Location Resolver if none were registered before. Then
// it creates a streaming device with scheduler type AK_SCHEDULER_DEFERRED_LINED_UP.
AKRESULT CAkDefaultIOHookDeferred::Init(
const AkDeviceSettings & in_deviceSettings, // Device settings.
bool in_bAsyncOpen/*=false*/ // If true, files are opened asynchronously when possible.
)
{
if ( in_deviceSettings.uSchedulerTypeFlags != AK_SCHEDULER_DEFERRED_LINED_UP )
{
AKASSERT( !"CAkDefaultIOHookDeferred I/O hook only works with AK_SCHEDULER_DEFERRED_LINED_UP devices" );
return AK_Fail;
}
m_bAsyncOpen = in_bAsyncOpen;
// If the Stream Manager's File Location Resolver was not set yet, set this object as the
// File Location Resolver (this I/O hook is also able to resolve file location).
if ( !AK::StreamMgr::GetFileLocationResolver() )
AK::StreamMgr::SetFileLocationResolver( this );
// Create a device in the Stream Manager, specifying this as the hook.
m_deviceID = AK::StreamMgr::CreateDevice( in_deviceSettings, this );
if ( m_deviceID != AK_INVALID_DEVICE_ID )
{
// Initialize structures needed to perform deferred transfers.
AkUInt32 uPoolSize = (AkUInt32)( in_deviceSettings.uMaxConcurrentIO * sizeof( OVERLAPPED ) );
m_poolID = AK::MemoryMgr::CreatePool( NULL, uPoolSize, sizeof( OVERLAPPED ), AkMalloc | AkFixedSizeBlocksMode );
if ( m_poolID == AK_INVALID_POOL_ID )
{
AKASSERT( !"Failed creating pool for asynchronous device" );
return AK_Fail;
}
AK_SETPOOLNAME( m_poolID, L"Deferred I/O hook" );
return AK_Success;
}
return AK_Fail;
}
void CAkDefaultIOHookDeferred::Term()
{
if ( AK::StreamMgr::GetFileLocationResolver() == this )
AK::StreamMgr::SetFileLocationResolver( NULL );
AK::StreamMgr::DestroyDevice( m_deviceID );
if ( m_poolID != AK_INVALID_POOL_ID )
{
AK::MemoryMgr::DestroyPool( m_poolID );
m_poolID = AK_INVALID_POOL_ID;
}
}
//
// IAkFileLocationAware implementation.
//-----------------------------------------------------------------------------
// Returns a file descriptor for a given file name (string).
AKRESULT CAkDefaultIOHookDeferred::Open(
const AkOSChar* in_pszFileName, // File name.
AkOpenMode in_eOpenMode, // Open mode.
AkFileSystemFlags * in_pFlags, // Special flags. Can pass NULL.
bool & io_bSyncOpen, // If true, the file must be opened synchronously. Otherwise it is left at the File Location Resolver's discretion. Return false if Open needs to be deferred.
AkFileDesc & out_fileDesc // Returned file descriptor.
)
{
// We normally consider that calls to ::CreateFile() on a hard drive are fast enough to execute in the
// client thread. If you want files to be opened asynchronously when it is possible, this device should
// be initialized with the flag in_bAsyncOpen set to true.
if ( io_bSyncOpen || !m_bAsyncOpen )
{
io_bSyncOpen = true;
// Get the full file path, using path concatenation logic.
AkOSChar szFullFilePath[AK_MAX_PATH];
if ( GetFullFilePath( in_pszFileName, in_pFlags, in_eOpenMode, szFullFilePath ) == AK_Success )
{
// Open the file with FILE_FLAG_OVERLAPPED and FILE_FLAG_NO_BUFFERING flags.
AKRESULT eResult = CAkFileHelpers::OpenFile(
szFullFilePath,
in_eOpenMode,
true,
true,
out_fileDesc.hFile );
if ( eResult == AK_Success )
{
ULARGE_INTEGER Temp;
Temp.LowPart = ::GetFileSize( out_fileDesc.hFile,(LPDWORD)&Temp.HighPart );
out_fileDesc.iFileSize = Temp.QuadPart;
out_fileDesc.uSector = 0;
out_fileDesc.deviceID = m_deviceID;
out_fileDesc.pCustomParam = ( in_eOpenMode == AK_OpenModeRead ) ? NULL : (void*)1;
out_fileDesc.uCustomParamSize = 0;
}
return eResult;
}
return AK_Fail;
}
else
{
// The client allows us to perform asynchronous opening.
// We only need to specify the deviceID, and leave the boolean to false.
out_fileDesc.iFileSize = 0;
out_fileDesc.uSector = 0;
out_fileDesc.deviceID = m_deviceID;
out_fileDesc.pCustomParam = NULL;
out_fileDesc.uCustomParamSize = 0;
return AK_Success;
}
}
// Returns a file descriptor for a given file ID.
AKRESULT CAkDefaultIOHookDeferred::Open(
AkFileID in_fileID, // File ID.
AkOpenMode in_eOpenMode, // Open mode.
AkFileSystemFlags * in_pFlags, // Special flags. Can pass NULL.
bool & io_bSyncOpen, // If true, the file must be opened synchronously. Otherwise it is left at the File Location Resolver's discretion. Return false if Open needs to be deferred.
AkFileDesc & out_fileDesc // Returned file descriptor.
)
{
// We normally consider that calls to ::CreateFile() on a hard drive are fast enough to execute in the
// client thread. If you want files to be opened asynchronously when it is possible, this device should
// be initialized with the flag in_bAsyncOpen set to true.
if ( io_bSyncOpen || !m_bAsyncOpen )
{
io_bSyncOpen = true;
// Get the full file path, using path concatenation logic.
AkOSChar szFullFilePath[AK_MAX_PATH];
if ( GetFullFilePath( in_fileID, in_pFlags, in_eOpenMode, szFullFilePath ) == AK_Success )
{
// Open the file with FILE_FLAG_OVERLAPPED and FILE_FLAG_NO_BUFFERING flags.
AKRESULT eResult = CAkFileHelpers::OpenFile(
szFullFilePath,
in_eOpenMode,
true,
true,
out_fileDesc.hFile );
if ( eResult == AK_Success )
{
ULARGE_INTEGER Temp;
Temp.LowPart = ::GetFileSize( out_fileDesc.hFile,(LPDWORD)&Temp.HighPart );
out_fileDesc.iFileSize = Temp.QuadPart;
out_fileDesc.uSector = 0;
out_fileDesc.deviceID = m_deviceID;
out_fileDesc.pCustomParam = NULL;
out_fileDesc.uCustomParamSize = 0;
}
return eResult;
}
return AK_Fail;
}
else
{
// The client allows us to perform asynchronous opening.
// We only need to specify the deviceID, and leave the boolean to false.
out_fileDesc.iFileSize = 0;
out_fileDesc.uSector = 0;
out_fileDesc.deviceID = m_deviceID;
out_fileDesc.pCustomParam = NULL;
out_fileDesc.uCustomParamSize = 0;
return AK_Success;
}
}
//
// IAkIOHookDeferred implementation.
//-----------------------------------------------------------------------------
// Local callback for overlapped I/O.
VOID CALLBACK CAkDefaultIOHookDeferred::FileIOCompletionRoutine(
DWORD dwErrorCode,
DWORD
#ifdef _DEBUG
dwNumberOfBytesTransfered
#endif
,
LPOVERLAPPED lpOverlapped
)
{
AkAsyncIOTransferInfo * pXferInfo = (AkAsyncIOTransferInfo*)(lpOverlapped->hEvent);
ReleaseOverlapped( lpOverlapped );
AKRESULT eResult = AK_Fail;
if ( ERROR_SUCCESS == dwErrorCode )
{
eResult = AK_Success;
AKASSERT( dwNumberOfBytesTransfered >= pXferInfo->uRequestedSize && dwNumberOfBytesTransfered <= pXferInfo->uBufferSize );
}
pXferInfo->pCallback( pXferInfo, eResult );
}
// Reads data from a file (asynchronous overload).
AKRESULT CAkDefaultIOHookDeferred::Read(
AkFileDesc & in_fileDesc, // File descriptor.
const AkIoHeuristics & /*in_heuristics*/, // Heuristics for this data transfer (not used in this implementation).
AkAsyncIOTransferInfo & io_transferInfo // Asynchronous data transfer info.
)
{
AKASSERT( in_fileDesc.hFile != INVALID_HANDLE_VALUE
&& io_transferInfo.uRequestedSize > 0
&& io_transferInfo.uBufferSize >= io_transferInfo.uRequestedSize );
// If this assert comes up, it might be beacause this hook's GetBlockSize() return value is incompatible
// with the system's handling of file reading for this specific file handle.
// If you are using the File Package extension, did you create your package with a compatible
// block size? It should be a multiple of WIN32_NO_BUFFERING_BLOCK_SIZE. (check -blocksize argument in the File Packager command line)
AKASSERT( ( io_transferInfo.uFilePosition % WIN32_NO_BUFFERING_BLOCK_SIZE ) == 0
|| !"Requested file position for I/O transfer is inconsistent with block size" );
OVERLAPPED * pOverlapped = GetFreeOverlapped( &io_transferInfo );
// Set file offset in OVERLAPPED structure.
pOverlapped->Offset = (DWORD)( io_transferInfo.uFilePosition & 0xFFFFFFFF );
pOverlapped->OffsetHigh = (DWORD)( ( io_transferInfo.uFilePosition >> 32 ) & 0xFFFFFFFF );
// File was open with asynchronous flag.
// Read overlapped.
// Note: With a file handle opened with FILE_FLAG_NO_BUFFERING, ::ReadFileEx() supports read sizes that go beyond the end
// of file. However, it does not support read sizes that are not a multiple of the drive's sector size.
// Since the buffer size is always a multiple of the block size, let's use io_transferInfo.uBufferSize
// instead of io_transferInfo.uRequestedSize.
if ( ::ReadFileEx( in_fileDesc.hFile,
io_transferInfo.pBuffer,
io_transferInfo.uBufferSize,
pOverlapped,
CAkDefaultIOHookDeferred::FileIOCompletionRoutine ) )
{
return AK_Success;
}
ReleaseOverlapped( pOverlapped );
return AK_Fail;
}
// Writes data to a file (asynchronous overload).
AKRESULT CAkDefaultIOHookDeferred::Write(
AkFileDesc & in_fileDesc, // File descriptor.
const AkIoHeuristics & /*in_heuristics*/, // Heuristics for this data transfer (not used in this implementation).
AkAsyncIOTransferInfo & io_transferInfo // Platform-specific asynchronous IO operation info.
)
{
AKASSERT( in_fileDesc.hFile != INVALID_HANDLE_VALUE
&& io_transferInfo.uRequestedSize > 0 );
// If this assert comes up, it might be beacause this hook's GetBlockSize() return value is incompatible
// with the system's handling of file reading for this specific file handle.
// Are you using the File Package Low-Level I/O with incompatible block size? (check -blocksize argument in the File Packager command line)
AKASSERT( io_transferInfo.uFilePosition % GetBlockSize( in_fileDesc ) == 0
|| !"Requested file position for I/O transfer is inconsistent with block size" );
OVERLAPPED * pOverlapped = GetFreeOverlapped( &io_transferInfo );
// Set file offset in OVERLAPPED structure.
pOverlapped->Offset = (DWORD)( io_transferInfo.uFilePosition & 0xFFFFFFFF );
pOverlapped->OffsetHigh = (DWORD)( ( io_transferInfo.uFilePosition >> 32 ) & 0xFFFFFFFF );
// File was open with asynchronous flag.
// Read overlapped.
if ( ::WriteFileEx( in_fileDesc.hFile,
io_transferInfo.pBuffer,
io_transferInfo.uRequestedSize,
pOverlapped,
CAkDefaultIOHookDeferred::FileIOCompletionRoutine ) )
{
return AK_Success;
}
ReleaseOverlapped( pOverlapped );
return AK_Fail;
}
// Cancel transfer(s).
void CAkDefaultIOHookDeferred::Cancel(
AkFileDesc & in_fileDesc, // File descriptor.
AkAsyncIOTransferInfo & /*io_transferInfo*/,// Transfer info to cancel (this implementation only handles "cancel all").
bool & io_bCancelAllTransfersForThisFile // Flag indicating whether all transfers should be cancelled for this file (see notes in function description).
)
{
if ( io_bCancelAllTransfersForThisFile )
{
CancelIo( in_fileDesc.hFile );
// Leave io_bCancelAllTransfersForThisFile to true: all transfers were cancelled for this file,
// so we don't need to be called again.
}
}
// Close a file.
AKRESULT CAkDefaultIOHookDeferred::Close(
AkFileDesc & in_fileDesc // File descriptor.
)
{
return CAkFileHelpers::CloseFile( in_fileDesc.hFile );
}
// Returns the block size for the file or its storage device.
AkUInt32 CAkDefaultIOHookDeferred::GetBlockSize(
AkFileDesc & in_fileDesc // File descriptor.
)
{
if ( in_fileDesc.pCustomParam == 0 )
return WIN32_NO_BUFFERING_BLOCK_SIZE;
return 1;
}
// Returns a description for the streaming device above this low-level hook.
void CAkDefaultIOHookDeferred::GetDeviceDesc(
AkDeviceDesc &
#ifndef AK_OPTIMIZED
out_deviceDesc // Description of associated low-level I/O device.
#endif
)
{
#ifndef AK_OPTIMIZED
AKASSERT( m_deviceID != AK_INVALID_DEVICE_ID || !"Low-Level device was not initialized" );
// Deferred scheduler.
out_deviceDesc.deviceID = m_deviceID;
out_deviceDesc.bCanRead = true;
out_deviceDesc.bCanWrite = true;
AKPLATFORM::SafeStrCpy( out_deviceDesc.szDeviceName, WIN32_DEFERRED_DEVICE_NAME, AK_MONITOR_DEVICENAME_MAXLENGTH );
out_deviceDesc.uStringSize = (AkUInt32)wcslen( out_deviceDesc.szDeviceName ) + 1;
#endif
}
// Returns custom profiling data: 1 if file opens are asynchronous, 0 otherwise.
// Tip: An interesting application for custom profiling data in deferred devices is to display
// the number of requests currently pending in the low-level IO.
AkUInt32 CAkDefaultIOHookDeferred::GetDeviceData()
{
return ( m_bAsyncOpen ) ? 1 : 0;
}
#endif