implementation module DataSources.SharedMemory

import System._WinBase, System._Pointer, System._Unsafe, StdInt, StdTuple, StdString, StdArray, StdBool, StdFunc, System.FilePath, Data.SharedDataSource, dynamic_string
import StdMisc

/**
* |--------------------------------------------|
* | size | value ptr | version | observer list |
* |--------------------------------------------|
*         |                     |               |-------------------|
*         |                      -------------> | observer 0 | next |
*         V                                     |-------------------|
*        |----------------------|                             |
*        | dynamic value string |                              --> ...
*        |----------------------|                 
*        <-------- size -------->
*/
sharedMemory :: !a !*envC -> (!Shared a *envS, !*envC) | MemoryEnv envC & MemoryEnv envS
sharedMemory v world
	# (heap, world)	= getProcessHeap world
	# initStr		= copy_to_string v
	# sStr			= size initStr
	# (mutx, world)	= createMutexA NULL False NULL world
	// check ok
	# (iptr, world)	= heapAlloc heap 0 (INT_SIZE * 4) world
	# (vptr, world)	= heapAlloc heap 0 sStr world
	# vptr			= writeCharArray vptr initStr
	# iptr			= writeIntElemOffset iptr 0 sStr	// init size of dynamic string
	# iptr			= writeIntElemOffset iptr 1 vptr	// init pointer to dynamic string
	# iptr			= writeIntElemOffset iptr 2 0		// init version number
	# iptr			= writeIntElemOffset iptr 3 NULL	// init linked list of observers
	= (createBasicDataSource "sharedMemory" (toString iptr) (mkOps heap mutx iptr) id const, world)
where
	get str = fst (copy_from_string {c \\ c <-: str})
	putback v _ = copy_to_string v
	
	mkOps heap mutx ptr env =
		({ read			= read
		, write			= write
		, getVersion	= getVersion
		, lock			= lock
		, lockExcl		= lockExcl
		, unlock		= unlock
		, close			= close
		, addObserver	= addObserver
		}, env)
	where
		read world
			# (sStr,ptr)	= readIntElemOffsetP ptr 0
			# (vptr,ptr)	= readIntElemOffsetP ptr 1
			# (ver,ptr)		= readIntElemOffsetP ptr 2
			# str			= derefCharArray vptr sStr
			# world			= forceEvalPointer ptr world
			# world			= forceEval str world
			= (Ok (fst (copy_from_string {c \\ c <-: str}), ver), world)
			
		write b world
			# dstr			= copy_to_string b
			# (vptr,ptr)	= readIntElemOffsetP ptr 1
			# (ok, world)	= heapFree heap 0 vptr world
			| not ok = (Error "writing to shared memory: error freeing memory", world)
			# sStr			= size dstr
			# (vptr, world)	= heapAlloc heap 0 sStr world
			| vptr == NULL = (Error "writing to shared memory: error allocating memory", world)
			# vptr			= writeCharArray vptr dstr
			# ptr			= writeIntElemOffset ptr 0 sStr
			# ptr			= writeIntElemOffset ptr 1 vptr
			// increase version number
			# ver			= readIntElemOffset ptr 2
			# ptr			= writeIntElemOffset ptr 2 (inc ver)
			// notify observers and empty list
			# (wptr,ptr)	= readIntElemOffsetP ptr 3
			# world			= notifyObservers wptr world
			# ptr			= writeIntElemOffset ptr 3 NULL
			= (Ok (), forceEvalPointer ptr world)
		where
			notifyObservers :: !Pointer !*env -> *env
			notifyObservers wptr world
				| wptr == NULL = world
				# obs 			= readIntElemOffset wptr 0
				# (_, world)	= setEvent obs world
				# next 			= readIntElemOffset wptr 1
				# world			= notifyObservers next world
				# (ok, world)	= heapFree heap 0 wptr world
				| not ok = abort "notifyWaiters: error freeing heap"
				= world

		getVersion world
			= (Ok (readIntElemOffset ptr 2), world)
		
		addObserver observer world
			# (nptr, world)	= heapAlloc heap 0 (INT_SIZE * 2) world
			# optr			= readIntElemOffset ptr 3
			# nptr			= writeIntElemOffset nptr 0 observer
			# nptr			= writeIntElemOffset nptr 1 optr
			# ptr			= writeIntElemOffset ptr 3 nptr
			= forceEvalPointer ptr world
			
		lock = lock`
		lockExcl = lock`
		lock` world
			# (r, world) = waitForSingleObject mutx INFINITE world
			| r <> WAIT_OBJECT_0 = abort "shared memory: error getting lock"
			= world
		
		unlock world
			# (ok, world) = releaseMutex mutx world
			| not ok = abort "shared memory: error releasing lock"
			= world
			
		close world = world
		
instance MemoryEnv World
where
	accMemory accFunc env
		# (a, mem) = accFunc 0
		= (a,env)
