unit ADCStreamExports;

interface

uses
	WinAPI.Windows, System.SysUtils, System.Classes,
	Core;

function ADC_ConfigureAcquire(iBoard: TBoardIndex; iSequencer: TSequencerIndex; NewChunkingType: TChunkingType; Chunk: TUInt32): TWinErrCode; cdecl;
function ADC_AcquireBlock(iBoard: TBoardIndex; iSequencer: TSequencerIndex; SampleCt: TUNative): TWinErrCode; cdecl;
function ADC_AcquireStream(iBoard: TBoardIndex; iSequencer: TSequencerIndex): TWinErrCode; cdecl;
function ADC_AcquireEnd(iBoard: TBoardIndex; iSequencer: TSequencerIndex): TWinErrCode; cdecl;
//The callback types take parameters similar to ADC_GetData.
function ADC_SetCallback(iBoard: TBoardIndex; iSequencer: TSequencerIndex; CallbackType: TCallbackType; pCallback: Pointer = nil): TWinErrCode; cdecl;
// header files overload ADC_GetDataV and ADC_GetDataCounts and ADC_GetDataRaw as ADC_GetData
function ADC_GetDataV(iBoard: TBoardIndex; iSequencer: TSequencerIndex; DataCt: TUInt32; var GotDataCt: TUInt32; pData: TPDouble = nil; pStatus: TPUInt16 = nil; pTimestamp: TPUInt16 = nil): TWinErrCode; cdecl;
function ADC_GetDataCounts(iBoard: TBoardIndex; iSequencer: TSequencerIndex; DataCt: TUInt32; var GotDataCt: TUInt32; pData: TPUInt16 = nil; pStatus: TPUInt16 = nil; pTimestamp: TPUInt16 = nil): TWinErrCode; cdecl;
function ADC_GetDataRaw(iBoard: TBoardIndex; iSequencer: TSequencerIndex; DataCt: TUInt32; var GotDataCt: TUInt32; pDataRaw: TPUInt32 = nil): TWinErrCode; cdecl;
function ADC_PollData(iBoard: TBoardIndex; iSequencer: TSequencerIndex): TWinErrCode; cdecl;
function ADC_ClearData(iBoard: TBoardIndex; iSequencer: TSequencerIndex): TWinErrCode; cdecl;



implementation

uses
	Math,
	PIOExports, ADCExports;

const
	BytesPerRow = 4; //The bare minimum ChunkSize is 1 row. Maybe we should impose a larger minimum?

type
	TAcquireMode = (amBlock, amStream);

	//TPCallbackProcCRaw = procedure (iBoard: TBoardIndex; iSequencer: TSequencerIndex; DataCt: TUInt32; pDataRaw: TPUInt32); cdecl;
	//TPCallbackProcCCounts = procedure (iBoard: TBoardIndex; iSequencer: TSequencerIndex; DataCt: TUInt32; pData: TPUInt16; pStatus: TPUInt16; pTimestamp: TPUInt16); cdecl;
	//TPCallbackProcCV   = procedure (iBoard: TBoardIndex; iSequencer: TSequencerIndex; DataCt: TUInt32; pData: TPDouble; pStatus: TPUInt16; pTimestamp: TPUInt16); cdecl;
	TPCallbackProcSRaw    = procedure (iBoard: TBoardIndex; iSequencer: TSequencerIndex; DataCt: TUInt32; pDataRaw: TPUInt32); stdcall;
	TPCallbackProcSCounts = procedure (iBoard: TBoardIndex; iSequencer: TSequencerIndex; DataCt: TUInt32; pData: TPUInt16; pStatus: TPUInt16; pTimestamp: TPUInt16); stdcall;
	TPCallbackProcSV      = procedure (iBoard: TBoardIndex; iSequencer: TSequencerIndex; DataCt: TUInt32; pData: TPDouble; pStatus: TPUInt16; pTimestamp: TPUInt16); stdcall;



type
	TPAcqChunkLink = ^TAcqChunkLink;
	TAcqChunkLink = record
		pPrev, pNext: TPAcqChunkLink;
		pData: Pointer;
	end;

	TChunkPool = class
	public
		hPoolSem: THandle;

	public
		constructor Create; virtual;
		destructor Destroy; override;
		procedure PushLast(pData: Pointer);
		procedure PushFirst(pData: Pointer);
		procedure PushKill;
		function PopLast: Pointer;
		function PopFirst: Pointer;
		function bGotData: Boolean;
	protected
		hPoolMutex: THandle;
		pFirst, pLast: TPAcqChunkLink;
	end;

function NewLink(pData: Pointer): TPAcqChunkLink;
begin
	Result := TPAcqChunkLink(LocalAlloc(LMEM_FIXED, SizeOf(Result^)));
	Result.pData := pData;
end;

function DisposeLink(pOldLink: TPAcqChunkLink): Pointer;
begin
	if pOldLink = nil then Exit(nil);

	Result := pOldLink.pData;
	pOldLink.pData := nil;
	LocalFree(THandle(pOldLink));
end;

{ TChunkPool }

function TChunkPool.bGotData: Boolean;
begin
	WaitForSingleObject(hPoolMutex, INFINITE);
		Result := pLast <> nil;
	ReleaseMutex(hPoolMutex);
end;

constructor TChunkPool.Create;
begin
	inherited;

	pLast := nil;
	hPoolMutex := CreateMutex(nil, False, nil);
	hPoolSem := INVALID_HANDLE_VALUE;
end;

destructor TChunkPool.Destroy;
var
	pLink: TPAcqChunkLink;
	pData: Pointer;

begin
	CloseAndInvalidateHandle(hPoolSem);
	repeat
		pLink := PopLast;
		if pLink = nil then Break;

		pData := DisposeLink(pLink);
		if pData <> nil then LocalFree(THandle(pData));
	until False;
	CloseAndInvalidateHandle(hPoolMutex);

	inherited;
end;

function TChunkPool.PopFirst: Pointer;
var
	pLink: TPAcqChunkLink;

begin
	if hPoolSem <> INVALID_HANDLE_VALUE then WaitForSingleObject(hPoolSem, INFINITE);

	WaitForSingleObject(hPoolMutex, INFINITE);
		pLink := pFirst;
		if pFirst <> nil then pFirst := pFirst.pNext;
		if pFirst = nil then
			pLast := nil
		else
			pFirst.pPrev := nil
		;
	ReleaseMutex(hPoolMutex);

	Result := DisposeLink(pLink);
end;

function TChunkPool.PopLast: Pointer;
var
	pLink: TPAcqChunkLink;

begin
	if hPoolSem <> INVALID_HANDLE_VALUE then WaitForSingleObject(hPoolSem, INFINITE);

	WaitForSingleObject(hPoolMutex, INFINITE);
		pLink := pLast;
		if pLast <> nil then pLast := pLast.pPrev;
		if pLast = nil then
			pFirst := nil
		else
			pLast.pNext := nil
		;
	ReleaseMutex(hPoolMutex);

	Result := DisposeLink(pLink);
end;

procedure TChunkPool.PushFirst(pData: Pointer);
var
	pLink: TPAcqChunkLink;

begin
	pLink := NewLink(pData);

	pLink.pPrev := nil;
	WaitForSingleObject(hPoolMutex, INFINITE);
		pLink.pNext := pFirst;
		if pFirst = nil then
			pLast := pLink
		else
			pFirst.pPrev := pLink
		;
		pFirst := pLink;
	ReleaseMutex(hPoolMutex);

	if hPoolSem <> INVALID_HANDLE_VALUE then ReleaseSemaphore(hPoolSem, 1, nil);
end;

procedure TChunkPool.PushKill;
begin
	ReleaseSemaphore(hPoolSem, 1, nil);
end;

procedure TChunkPool.PushLast(pData: Pointer);
var
	pLink: TPAcqChunkLink;

begin
	pLink := NewLink(pData);

	pLink.pNext := nil;
	WaitForSingleObject(hPoolMutex, INFINITE);
		pLink.pPrev := pLast;
		if pLast = nil then
			pFirst := pLink
		else
			pLast.pNext := pLink
		;
		pLast := pLink;
	ReleaseMutex(hPoolMutex);

	if hPoolSem <> INVALID_HANDLE_VALUE then ReleaseSemaphore(hPoolSem, 1, nil);
end;

{  }

type
	TPoolCallbackThread = class;
	TPoolAcquireThread = class;
	TPoolFillThread = class;

	TPoolCallbackThread = class(TBasalThread)
	public
		iBoard: TBoardIndex;
		iSequencer: TSequencerIndex;
		DataChunkPool: TChunkPool;
		CallbackType: TCallbackType;
		pCallbackRaw   : TPCallbackProcSRaw;
		pCallbackCounts: TPCallbackProcSCounts;
		pCallbackV     : TPCallbackProcSV;
		AcquireThread: TPoolAcquireThread;

		constructor Create; override;
		destructor Destroy; override;
	protected
		procedure Execute; override;
		procedure CallCallback(pData: TPUInt32);
	end;

	TPoolAcquireThread = class(TBasalThread)
	public
		iBoard: TBoardIndex;
		ChunkSize: TUNative;
		EmptyChunkPool, FinalChunkPool: TChunkPool;
		CallbackThread: TPoolCallbackThread;
		AcquireMode: TAcquireMode;
		BlockSampleCt: TUNative;

		constructor Create; override;
		destructor Destroy; override;
		procedure RecycleChunk(pData: TPUInt32);
	protected
		FillThread: TPoolFillThread;
		procedure Execute; override;
	end;

	TPoolFillThread = class(TBasalThread)
	public
		hFillEvent: THandle;
		AcquireThread: TPoolAcquireThread;
	protected
		procedure Execute; override;
	end;

{ TPoolAcquireThread }

constructor TPoolAcquireThread.Create;
begin
	inherited;

	EmptyChunkPool := TChunkPool.Create;
end;

destructor TPoolAcquireThread.Destroy;
begin
	DoDMAPiongControl(iBoard, #$10);

	FreeAndNil(EmptyChunkPool);
	FreeAndNil(FinalChunkPool);

	inherited;
end;

procedure TPoolAcquireThread.Execute;
//ADIO/HMAIO parameters.
const
	IRQEnableOffset = $40;

	ADCCmd_Go = $10000;

	IRQEn_DMADone = $0004;
	IRQEn_FIFOAF = $0010;

	procedure TerminateCallbackThread;
	var
		OldCallbackThread: TPoolCallbackThread;

	begin
		WithSeqThreadingMutexHeld(iBoard, 0, procedure () begin
			OldCallbackThread := CallbackThread;
			CallbackThread := nil;
		end);
		if OldCallbackThread <> nil then begin
			OldCallbackThread.FreeOnTerminate := True;
			OldCallbackThread.DataChunkPool.PushKill;
			//OldCallbackThread.Free;
		end;
	end;

	procedure StopCard(StartStop: TUInt32);
	begin
		PIO_Out32(iBoard, ADCStartStopOffset + dSequencer*0, StartStop and not ADCCmd_Go);
		PIO_Out32(iBoard, IRQEnableOffset, 0);
		DoDMAPiongControl(iBoard, #$10);
	end;

	function GetAChunk: TPUInt32;
	begin
		Result := EmptyChunkPool.PopLast;
		if Result = nil then Result := TPUInt32(LocalAlloc(LMEM_FIXED, ChunkSize));
		if not EmptyChunkPool.bGotData then SetEvent(FillThread.hFillEvent);
	end;

var
	Rows: TUNative;
	Status: TWinErrCode;
	pData: TPUInt32;
	AFLevel: TUInt32;
	StartStop: TUInt32;
	iSequencer: TSequencerIndex;
	Ovl: TOverlapped;
	DummySize: TUInt32;

begin
	StartStop := PIO_In32(iBoard, ADCStartStopOffset) or $20000 or ADCCmd_Continuous;

	try
		FillThread := TPoolFillThread.Create;
		FillThread.AcquireThread := Self;
		FillThread.Start;

		if CallbackThread <> nil then begin
			CallbackThread.AcquireThread := Self;
			CallbackThread.Start;
		end;

		ZeroMemory(@Ovl, SizeOf(Ovl));
		Ovl.hEvent := CreateEvent(nil, False, False, nil);

		SetThreadPriority(GetCurrentThread, THREAD_PRIORITY_TIME_CRITICAL);

		AFLevel := PIO_In32(iBoard, AFLevelOffset);
		ChunkSize := AFLevel;

		DoDMAPiongControl(iBoard, #$11);

		pData := GetAChunk;
		Status := PIO_DMAPiongIn(iBoard, TPUInt8(pData), ChunkSize, @Ovl);

		//PIO_Out32(iBoard, IRQEnableOffset, IRQEn_DMADone);
		PIO_Out32(iBoard, IRQEnableOffset, IRQEn_FIFOAF);
		for iSequencer := High(Dev[iBoard].SequencerAcq) downto 1 do
			PIO_Out32(iBoard, ADCStartStopOffset + dSequencer*iSequencer, StartStop and not ADCCmd_Go)
		;
		PIO_Out32(iBoard, ADCStartStopOffset + dSequencer*0, StartStop or ADCCmd_Go);
		repeat
			if Status = ERROR_IO_PENDING then begin
				if GetOverlappedResult(Dev[iBoard].hDevice, Ovl, DummySize, True) then begin
					Status := ERROR_SUCCESS;
				end
				else begin
					Status := GetLastError;
					if Status = ERROR_SUCCESS then Status := ERROR_GEN_FAILURE;
				end;
			end;

			if Status <> ERROR_SUCCESS then begin
				ReturnValue := Status;
				Break;
			end;

			if FinalChunkPool <> nil then
				FinalChunkPool.PushLast(pData)
			else begin
				CallbackThread.DataChunkPool.PushLast(pData);
				{$IFDEF DEBUG}
					//AnsiMessageBox('Passed data to callback thread.', 'AIOAIO', MB_ICONINFORMATION);
				{$ENDIF}
			end;
			if AcquireMode = amBlock then begin
				Rows := ChunkSize div BytesPerRow;
				if Rows >= BlockSampleCt then begin
					ReturnValue := ERROR_SUCCESS;
					Break;
				end;
				Dec(BlockSampleCt, Rows);
			end;

			if Terminated then begin
				if ReturnValue = ERROR_SUCCESS then ReturnValue := ERROR_OPERATION_ABORTED;
				Break;
			end;

			Status := PIO_DMAPiongIn(iBoard, TPUInt8(pData), ChunkSize, @Ovl);
		until False;
		StopCard(StartStop);

		CloseHandle(Ovl.hEvent);

		TerminateCallbackThread;

		SetThreadPriority(GetCurrentThread, THREAD_PRIORITY_NORMAL);

		FillThread.Terminate;
		SetEvent(FillThread.hFillEvent);
		FillThread.Free;

		{$IFDEF DEBUG}
			if ReturnValue <> ERROR_OPERATION_ABORTED then
				AnsiMessageBox('TPoolAcquireThread exiting cleanly, returning ' + AnsiString(IntToStr(ReturnValue)) + '.', 'AIOAIO', MB_ICONEXCLAMATION)
			;
		{$ENDIF}
	except
		on E: Exception do begin
			ReturnValue := ERROR_INTERNAL_ERROR;

			StopCard(StartStop);

			{$IFDEF DEBUG}
				AnsiMessageBox(AnsiString(E.ClassName) + ' in TPoolAcquireThread.Execute: ' + AnsiString(E.Message), 'AIOAIO', MB_ICONEXCLAMATION);
			{$ENDIF}
		end;
	end;
end;

procedure TPoolAcquireThread.RecycleChunk(pData: TPUInt32);
begin
	EmptyChunkPool.PushLast(pData);
end;

{ TPoolCallbackThread }

procedure TPoolCallbackThread.CallCallback(pData: TPUInt32);
var
	DataRaw   : array of TUInt16;
	DataCounts: array of TUInt16; pDataCounts: TPUInt16;
	DataV     : array of Double ; pDataV     : TPDouble;
	Status    : array of TUInt16; pStatus    : TPUInt16;
	Timestamp : array of TUInt16; pTimestamp : TPUInt16;
	RowCt, iRow: TSNative;
	pRow: TPUInt32;

begin
	RowCt := AcquireThread.ChunkSize div BytesPerRow;
	if RowCt <= 0 then Exit;

	pRow := pData;

	pDataCounts := nil;
	pDataV := nil;
	pStatus := nil;
	pTimestamp := nil;
	case CallbackType of
		ctRaw: begin
			SetLength(DataRaw, RowCt);
		end;
		ctCounts: begin
			SetLength(DataCounts, RowCt); pDataCounts := @DataCounts[0];
			SetLength(Status, RowCt);     pStatus := @Status[0];
			SetLength(Timestamp, RowCt);  pTimestamp := @Timestamp[0];
		end;
		ctV: begin
			SetLength(DataV, RowCt);      pDataV := @DataV[0];
			SetLength(Status, RowCt);     pStatus := @Status[0];
			SetLength(Timestamp, RowCt);  pTimestamp := @Timestamp[0];
		end;
		else begin
			Exit;
		end;
	end;
	if CallbackType <> ctRaw then
		for iRow := 0 to RowCt-1 do begin
			ParseADRow(pRow^, pStatus, pTimestamp, pDataCounts, pDataV);
			Inc(pRow);
			if pDataCounts <> nil then Inc(pDataCounts);
			if pDataV      <> nil then Inc(pDataV);
			if pStatus     <> nil then Inc(pStatus);
			if pTimestamp  <> nil then Inc(pTimestamp);
		end
	;

	{$IFDEF DEBUG}
		//AnsiMessageBox('Calling callback.', 'AIOAIO', MB_ICONINFORMATION);
	{$ENDIF}

	Dev[iBoard].SequencerAcq[iSequencer].bInCallback := True;
	try
		case CallbackType of
			ctRaw: pCallbackRaw(iBoard, iSequencer, RowCt, @DataRaw[0]);
			ctCounts: pCallbackCounts(iBoard, iSequencer, RowCt, @DataCounts[0], @Status[0], @Timestamp[0]);
			ctV: pCallbackV(iBoard, iSequencer, RowCt, @DataV[0], @Status[0], @Timestamp[0]);
		end;
	except
	end;
	Dev[iBoard].SequencerAcq[iSequencer].bInCallback := False;

	SetLength(DataRaw, 0);
	SetLength(DataCounts, 0);
	SetLength(DataV, 0);
	SetLength(Status, 0);
	SetLength(Timestamp, 0);
end;

constructor TPoolCallbackThread.Create;
begin
	inherited;

	DataChunkPool := TChunkPool.Create;
end;

destructor TPoolCallbackThread.Destroy;
begin
	FreeAndNil(DataChunkPool);

	inherited;
end;

procedure TPoolCallbackThread.Execute;
var
	pData: TPUInt32;

begin
	DataChunkPool.hPoolSem := CreateSemaphore(nil, 0, $FFFFFF, nil);

	repeat
		pData := DataChunkPool.PopFirst;
		if pData = nil then
			Terminate
		;
		if Terminated then Break;

		CallCallback(pData);
		AcquireThread.RecycleChunk(pData);
	until False;
end;

{ TPoolFillThread }

procedure TPoolFillThread.Execute;
var
	pData: TPUInt32;

begin
	SetThreadPriority(GetCurrentThread, THREAD_PRIORITY_NORMAL);

	//Because we fill one before waiting, we get one "free". Setting the initial state here gives a second "free".
	hFillEvent := CreateEvent(nil, False, True, nil);
	repeat
		//Fill in by one.
		pData := TPUInt32(LocalAlloc(LMEM_FIXED, AcquireThread.ChunkSize));

		SetThreadPriority(GetCurrentThread, THREAD_PRIORITY_HIGHEST);
			AcquireThread.EmptyChunkPool.PushLast(pData);
		SetThreadPriority(GetCurrentThread, THREAD_PRIORITY_NORMAL);

		//Wait until another is needed.
		WaitForSingleObject(hFillEvent, INFINITE);
	until Terminated;
	CloseAndInvalidateHandle(hFillEvent);
end;

{  }

function ADC_ConfigureAcquire(iBoard: TBoardIndex; iSequencer: TSequencerIndex; NewChunkingType: TChunkingType; Chunk: TUInt32): TWinErrCode;
var
	Status: TWinErrCode;

begin
	Result := ERROR_NOT_SUPPORTED;
	Exit;

	Result := ValidateBoardAndSequencerIndex(iBoard, iSequencer);
	if Result <> ERROR_SUCCESS then Exit;

	try
		case NewChunkingType of
			ctAuto: ;
			ctSize, ctRate: if (Chunk < 1) or (Chunk > Dev[iBoard].BaseClock) then begin
				Result := ERROR_INVALID_DATA;
				Exit;
			end;
			else begin
				Result := ERROR_INVALID_FLAGS;
				Exit;
			end;
		end;

		Status := ERROR_INTERNAL_ERROR;
		WithSeqThreadingMutexHeld(iBoard, iSequencer, procedure () begin
			with Dev[iBoard].SequencerAcq[iSequencer] do begin
				if AcquireThread <> nil then begin
					Status := ERROR_BUSY;
				end
				else begin
					ChunkingType := NewChunkingType;
					ChunkingScalar := Chunk;
					Status := ERROR_SUCCESS;
				end;
			end;
		end);
		Result := Status;
	except
		Result := ERROR_INTERNAL_ERROR;
	end;
end;

function ADC_Acquire_Inner(iBoard: TBoardIndex; iSequencer: TSequencerIndex; SampleCt: TUNative = 0): TWinErrCode;
var
	NewAcquireThread: TPoolAcquireThread;
	ChunkSize: TUInt32;
	//Divisor: TUInt32;
	AFLevel: TUInt32;
	Status: TWinErrCode;
	ThisCallbackType: TCallbackType;
	pThisCallback: Pointer;
	ThisCallbackThread: TPoolCallbackThread;
	ThisFinalChunkPool: TChunkPool;
	ThisAcquireMode: TAcquireMode;

begin
	Result := ValidateBoardIndex(iBoard);
	if Result <> ERROR_SUCCESS then Exit;

	if iSequencer <> 0 then begin
		Result := ERROR_INVALID_INDEX;
		Exit;
	end;

	if not (Dev[iBoard].RegisterMap in [rmADIO, rmHMAIO]) then begin
		Result := ERROR_BAD_DEV_TYPE;
		Exit;
	end;

	{
	case Dev[iBoard].SequencerAcq[iSequencer].ChunkingType of
		ctAuto: begin
			Divisor := PIO_In32(iBoard, DivisorOffset + dSequencer*iSequencer);
			AFLevel := PIO_In32(iBoard, AFLevelOffset + dSequencer*iSequencer);
			ChunkSize := Round(Dev[iBoard].BaseClock / Divisor / 10 / AFLevel) * AFLevel;
			if ChunkSize < AFLevel then ChunkSize := AFLevel;
		end;
		ctSize: begin
			ChunkSize := Dev[iBoard].SequencerAcq[iSequencer].ChunkingScalar;
			AFLevel := PIO_In32(iBoard, AFLevelOffset + dSequencer*iSequencer);
			//`@ Compare ChunkSize and AFLevel to determine "mode".
		end;
		ctRate: begin
			Divisor := PIO_In32(iBoard, DivisorOffset + dSequencer*iSequencer);
			if Divisor = 0 then begin
				ChunkSize := 2048;
			end
			else begin
				ChunkSize := Round(Dev[iBoard].BaseClock / Divisor / Dev[iBoard].SequencerAcq[iSequencer].ChunkingScalar);
			end;
		end;
	end;
	}
	AFLevel := PIO_In32(iBoard, AFLevelOffset + dSequencer*iSequencer);
	ChunkSize := AFLevel * 8;

	if SampleCt = 0 then
		ThisAcquireMode := amStream
	else
		ThisAcquireMode := amBlock
	;

	Status := ERROR_INTERNAL_ERROR;
	WithSeqThreadingMutexHeld(iBoard, iSequencer, procedure () begin
		if Dev[iBoard].SequencerAcq[iSequencer].AcquireThread <> nil then begin
			Status := ERROR_BUSY;
			Exit;
		end;

		pThisCallback := Dev[iBoard].SequencerAcq[iSequencer].pCallback;
		ThisCallbackType := Dev[iBoard].SequencerAcq[iSequencer].CallbackType;
		case ThisCallbackType of
			ctRaw, ctCounts, ctV: ;
			else pThisCallback := nil;
		end;
		if pThisCallback = nil then ThisCallbackType := ctNone;

		if pThisCallback = nil then begin
			ThisCallbackThread := nil;

			ThisFinalChunkPool := TChunkPool.Create;
		end
		else begin
			ThisFinalChunkPool := nil;

			ThisCallbackThread := TPoolCallbackThread.Create;
			ThisCallbackThread.iBoard := iBoard;
			ThisCallbackThread.iSequencer := iSequencer;
			ThisCallbackThread.CallbackType := ThisCallbackType;
			case ThisCallbackType of
				ctRaw: ThisCallbackThread.pCallbackRaw := pThisCallback;
				ctCounts: ThisCallbackThread.pCallbackCounts := pThisCallback;
				ctV: ThisCallbackThread.pCallbackV := pThisCallback;
			end;
		end;

		NewAcquireThread := TPoolAcquireThread.Create;
		NewAcquireThread.iBoard := iBoard;
		NewAcquireThread.ChunkSize := ChunkSize;
		NewAcquireThread.CallbackThread := ThisCallbackThread;
		NewAcquireThread.FinalChunkPool := ThisFinalChunkPool;
		NewAcquireThread.AcquireMode := ThisAcquireMode;
		NewAcquireThread.BlockSampleCt := SampleCt;

		Dev[iBoard].SequencerAcq[iSequencer].AcquireThread := NewAcquireThread;
		NewAcquireThread.Start;
		Status := ERROR_SUCCESS;
	end);
	Result := Status;
end;

function ADC_AcquireBlock(iBoard: TBoardIndex; iSequencer: TSequencerIndex; SampleCt: TUNative): TWinErrCode;
begin
	try
		if SampleCt <= 0 then begin
			//Successfully do nothing, as requested.
			Result := ERROR_SUCCESS;
			Exit;
		end;

		Result := ADC_Acquire_Inner(iBoard, iSequencer, SampleCt);
	except
		Result := ERROR_INTERNAL_ERROR;
	end;
end;

function ADC_AcquireStream(iBoard: TBoardIndex; iSequencer: TSequencerIndex): TWinErrCode;
begin
	try
		Result := ADC_Acquire_Inner(iBoard, iSequencer);
	except
		on E: Exception do begin
			Result := ERROR_INTERNAL_ERROR;
			{$IFDEF DEBUG}
				AnsiMessageBox(AnsiString(E.ClassName) + ' in ADC_AcquireStream: ' + AnsiString(E.Message), 'AIOAIO', MB_ICONEXCLAMATION);
			{$ENDIF}
		end;
	end;
end;

function ADC_AcquireEnd(iBoard: TBoardIndex; iSequencer: TSequencerIndex): TWinErrCode;
var
	OldAcquireThread: TThread;

begin
	Result := ValidateBoardAndSequencerIndex(iBoard, iSequencer);
	if Result <> ERROR_SUCCESS then Exit;

	try
		WithSeqThreadingMutexHeld(iBoard, iSequencer, procedure () begin
			with Dev[iBoard].SequencerAcq[iSequencer] do begin
				if AcquireThread <> nil then OldAcquireThread := AcquireThread;
				AcquireThread := nil;
			end;
		end);

		if OldAcquireThread = nil then begin
			Result := ERROR_NOT_READY;
		end
		else begin
			OldAcquireThread.Terminate;
			OldAcquireThread.Free;
		end;
	except
		Result := ERROR_INTERNAL_ERROR;
	end;
end;

function ADC_SetCallback(iBoard: TBoardIndex; iSequencer: TSequencerIndex; CallbackType: TCallbackType; pCallback: Pointer = nil): TWinErrCode;
var
	Status: TWinErrCode;

begin
	Result := ValidateBoardAndSequencerIndex(iBoard, iSequencer);
	if Result <> ERROR_SUCCESS then Exit;

	try
		case CallbackType of
			ctRaw, ctCounts, ctV: ;
			ctNone: pCallback := nil;
			else begin
				Result := ERROR_INVALID_HOOK_FILTER;
				Exit;
			end;
		end;

		if pCallback = nil then CallbackType := ctNone;

		Status := ERROR_INTERNAL_ERROR;
		WithSeqThreadingMutexHeld(iBoard, iSequencer, procedure () begin
			if Dev[iBoard].SequencerAcq[iSequencer].AcquireThread <> nil then begin
				Status := ERROR_BUSY;
			end
			else begin
				Dev[iBoard].SequencerAcq[iSequencer].CallbackType := CallbackType;
				Dev[iBoard].SequencerAcq[iSequencer].pCallback := pCallback;
				Status := ERROR_SUCCESS;
			end;
		end);
		Result := Status;
	except
		Result := ERROR_INTERNAL_ERROR;
	end;
end;

function ADC_GetData_Inner(iBoard: TBoardIndex; iSequencer: TSequencerIndex; DataCt: TUInt32; var GotDataCt: TUInt32; pStatus: TPUInt16; pTimestamp: TPUInt16; pDataRaw: TPUInt32; pDataCounts: TPUInt16; pDataV: TPDouble): TWinErrCode;
begin
	Result := ERROR_NOT_SUPPORTED;
{
var
	Status: TWinErrCode;
	ThisChunkPool: TChunkPool;
	pChunk: TPAcqChunkHeader;
	pChunkData, pThisChunkData: TPUInt32;
	ThisGotDataCt, ThisDataLeft, FinalDataCt, iData: TUInt32;
	bRecycle: Boolean;

begin
	Result := ERROR_SUCCESS;
	if DataCt <= 0 then Exit; //Successful NOP.

	try
		GotDataCt := 0;
		if pStatus     <> nil then ZeroMemory(pStatus    , SizeOf(pStatus    ^) * DataCt);
		if pTimestamp  <> nil then ZeroMemory(pTimestamp , SizeOf(pTimestamp ^) * DataCt);
		if pDataRaw    <> nil then ZeroMemory(pDataRaw   , SizeOf(pDataRaw   ^) * DataCt);
		if pDataCounts <> nil then ZeroMemory(pDataCounts, SizeOf(pDataCounts^) * DataCt);
		if pDataV      <> nil then ZeroMemory(pDataV     , SizeOf(pDataV     ^) * DataCt);
	except
		Result := ERROR_INVALID_ADDRESS;
		Exit;
	end;

	FinalDataCt := 0;
	Status := ERROR_INTERNAL_ERROR;
	repeat
		pChunk := nil;
		WithSeqThreadingMutexHeld(iBoard, iSequencer, procedure () begin
			if Dev[iBoard].SequencerAcq[iSequencer].AcquireThread = nil then begin
				Status := ERROR_NOT_READY;
				Exit;
			end;

			ThisChunkPool := TPoolAcquireThread(Dev[iBoard].SequencerAcq[iSequencer].AcquireThread).FinalChunkPool;
			if ThisChunkPool = nil then begin
				Status := ERROR_INVALID_FILTER_PROC;
				Exit;
			end;

			pChunk := ThisChunkPool.PopFirst;
		end);
		if pChunk = nil then Break;

		ThisDataLeft := pChunk.DataUsed div BytesPerRow;
		ThisGotDataCt := Min(DataCt, ThisDataLeft);
		pChunkData := GetChunkData(pChunk);
		if pDataRaw <> nil then begin
			Move(pChunkData^, pDataRaw^, BytesPerRow * ThisGotDataCt);
			//Inc(pDataRaw, ThisGotDataCt);
		end;
		if (pStatus <> nil) or (pTimestamp <> nil) or (pDataCounts <> nil) or (pDataV <> nil) then begin
			pThisChunkData := pChunkData;
			for iData := 1 to ThisGotDataCt do begin
				ParseADRow(pThisChunkData^, pStatus, pTimestamp, pDataCounts, pDataV);
				Inc(pThisChunkData);
				if pStatus     <> nil then Inc(pStatus);
				if pTimestamp  <> nil then Inc(pTimestamp);
				if pDataCounts <> nil then Inc(pDataCounts);
				if pDataV      <> nil then Inc(pDataV);
			end;
		end;
		Dec(DataCt, ThisGotDataCt);
		Dec(ThisDataLeft, ThisGotDataCt);
		Inc(FinalDataCt, ThisGotDataCt);

		bRecycle := True;
		if ThisDataLeft > 0 then begin
			//There's data we didn't consume, so put it back.
			ThisGotDataCt := (pChunk.DataUsed div BytesPerRow) - ThisDataLeft;
			if ThisGotDataCt > 0 then begin
				//We consumed some of the data, so just put back the rest.
				pChunkData := GetChunkData(pChunk);
				pThisChunkData := pChunkData;
				Inc(pThisChunkData, ThisGotDataCt);
				pChunk.DataUsed := ThisDataLeft * BytesPerRow;
				Move(pThisChunkData^, pChunkData^, pChunk.DataUsed);
			end;
			WithSeqThreadingMutexHeld(iBoard, iSequencer, procedure ()
			var AcquireThread: TPoolAcquireThread;
			begin
				AcquireThread := TPoolAcquireThread(Dev[iBoard].SequencerAcq[iSequencer].AcquireThread);
				if AcquireThread = nil then Exit;

				bRecycle := False;
				AcquireThread.FinalChunkPool.PushFirst(pChunk);
			end);
		end;
		if bRecycle then WithThreadingMutexHeld(iBoard, iSequencer, procedure ()
		var AcquireThread: TPoolAcquireThread;
		begin
			AcquireThread := TPoolAcquireThread(Dev[iBoard].SequencerAcq[iSequencer].AcquireThread);
			if AcquireThread = nil then Exit;

			bRecycle := False;
			AcquireThread.RecycleChunk(pChunk);
		end);
		if bRecycle then DisposeChunk(pChunk);

		if DataCt <= 0 then Break;
		if Status <> ERROR_INTERNAL_ERROR then Break;
	until False;
	if Status = ERROR_INTERNAL_ERROR then begin
		if FinalDataCt = 0 then
			Status := ERROR_HANDLE_EOF
		else
			Status := ERROR_SUCCESS
		;
	end;
	Result := Status;
	GotDataCt := FinalDataCt;
}
end;

function ADC_GetDataV(iBoard: TBoardIndex; iSequencer: TSequencerIndex; DataCt: TUInt32; var GotDataCt: TUInt32; pData: TPDouble = nil; pStatus: TPUInt16 = nil; pTimestamp: TPUInt16 = nil): TWinErrCode;
begin
	Result := ValidateBoardAndSequencerIndex(iBoard, iSequencer);
	if Result <> ERROR_SUCCESS then Exit;

	try
		Result := ADC_GetData_Inner(iBoard, iSequencer, DataCt, GotDataCt, pStatus, pTimestamp, nil, nil, pData);
	except
		Result := ERROR_INTERNAL_ERROR;
	end;
end;

function ADC_GetDataCounts(iBoard: TBoardIndex; iSequencer: TSequencerIndex; DataCt: TUInt32; var GotDataCt: TUInt32; pData: TPUInt16 = nil; pStatus: TPUInt16 = nil; pTimestamp: TPUInt16 = nil): TWinErrCode;
begin
	Result := ValidateBoardAndSequencerIndex(iBoard, iSequencer);
	if Result <> ERROR_SUCCESS then Exit;

	try
		Result := ADC_GetData_Inner(iBoard, iSequencer, DataCt, GotDataCt, pStatus, pTimestamp, nil, pData, nil);
	except
		Result := ERROR_INTERNAL_ERROR;
	end;
end;

function ADC_GetDataRaw(iBoard: TBoardIndex; iSequencer: TSequencerIndex; DataCt: TUInt32; var GotDataCt: TUInt32; pDataRaw: TPUInt32 = nil): TWinErrCode;
begin
	Result := ValidateBoardAndSequencerIndex(iBoard, iSequencer);
	if Result <> ERROR_SUCCESS then Exit;

	try
		Result := ADC_GetData_Inner(iBoard, iSequencer, DataCt, GotDataCt, nil, nil, pDataRaw, nil, nil);
	except
		Result := ERROR_INTERNAL_ERROR;
	end;
end;

function ADC_PollData(iBoard: TBoardIndex; iSequencer: TSequencerIndex): TWinErrCode;
var
	Status: TWinErrCode;

begin
	Result := ValidateBoardAndSequencerIndex(iBoard, iSequencer);
	if Result <> ERROR_SUCCESS then Exit;

	try
		Status := ERROR_INTERNAL_ERROR;
		WithSeqThreadingMutexHeld(iBoard, iSequencer, procedure () begin
			if Dev[iBoard].SequencerAcq[iSequencer].AcquireThread = nil then
				Status := ERROR_NOT_READY
			else if TPoolAcquireThread(Dev[iBoard].SequencerAcq[iSequencer].AcquireThread).FinalChunkPool = nil then
				Status := ERROR_INVALID_FILTER_PROC
			else if not TPoolAcquireThread(Dev[iBoard].SequencerAcq[iSequencer].AcquireThread).FinalChunkPool.bGotData then
				Status := ERROR_HANDLE_EOF
			else
				Status := ERROR_SUCCESS
			;
		end);
		Result := Status;
	except
		Result := ERROR_INTERNAL_ERROR;
	end;
end;

function ADC_ClearData(iBoard: TBoardIndex; iSequencer: TSequencerIndex): TWinErrCode;
var
	Status: TWinErrCode;
	pLink, pNextLink: TPAcqChunkLink;
	pData: TPUInt32;
	ThisChunkPool: TChunkPool;

begin
	Result := ValidateBoardAndSequencerIndex(iBoard, iSequencer);
	if Result <> ERROR_SUCCESS then Exit;

	try
		pLink := nil;
		Status := ERROR_INTERNAL_ERROR;
		WithSeqThreadingMutexHeld(iBoard, iSequencer, procedure () begin
			if Dev[iBoard].SequencerAcq[iSequencer].AcquireThread = nil then begin
				Status := ERROR_NOT_READY;
				Exit;
			end;

			ThisChunkPool := TPoolAcquireThread(Dev[iBoard].SequencerAcq[iSequencer].AcquireThread).FinalChunkPool;
			if ThisChunkPool = nil then begin
				Status := ERROR_INVALID_FILTER_PROC;
				Exit;
			end;

			pLink := ThisChunkPool.pLast;
			ThisChunkPool.pFirst := nil;
			ThisChunkPool.pLast := nil;
		end);
		Result := Status;
		if Result <> ERROR_SUCCESS then Exit;

		if pLink = nil then begin
			Result := ERROR_HANDLE_EOF;
			Exit;
		end;

		while pLink <> nil do begin
			pNextLink := pLink.pPrev;
			pData := DisposeLink(pLink);
			if pData <> nil then LocalFree(THandle(pData));
			pLink := pNextLink;
		end;
	except
		Result := ERROR_INTERNAL_ERROR;
	end;
end;

end.