#lang racket
(require (for-syntax syntax/parse)
"environment.rkt"
"control.rkt"
"queue.rkt")
(struct allocation (process
units)
#:mutable)
(struct resource (units
units-available
units-allocated
satisfied
queue)
#:mutable)
(define (make-resource (units 1))
(resource
units units 0 (make-queue) (make-queue)))
(define (resource-process-allocation resource process)
(let/ec exit
(queue-for-each (resource-satisfied resource)
(lambda (allocation)
(when (eq? (allocation-process allocation) process)
(exit allocation))))
#f))
(define (resource-process-queue resource process)
(let/ec exit
(queue-for-each (resource-queue resource)
(lambda (allocation)
(when (eq? (allocation-process allocation) process)
(exit allocation))))
#f))
(define (resource-allocate resource units process)
(let ((process-allocation (resource-process-allocation resource process)))
(if process-allocation
(set-allocation-units!
process-allocation
(+ (allocation-units process-allocation) units))
(queue-insert-last!
(resource-satisfied resource)
(allocation process units)))
(set-resource-units-available!
resource
(- (resource-units-available resource) units))
(set-resource-units-allocated!
resource
(+ (resource-units-allocated resource) units))))
(define (resource-deallocate resource units process)
(let ((process-allocation (resource-process-allocation resource process)))
(if (< units (allocation-units process-allocation))
(set-allocation-units!
process-allocation
(- (allocation-units process-allocation) units))
(queue-remove! (resource-satisfied resource)
process-allocation))
(set-resource-units-available!
resource
(+ (resource-units-available resource) units))
(set-resource-units-allocated!
resource
(- (resource-units-allocated resource) units))))
(define (resource-request resource (units 1) (priority -inf.0) (reneg #f))
(let ((process (current-simulation-process)))
(when (not process)
(error 'resource-request
"no current process"))
(let ((process-allocation (resource-process-allocation resource process)))
(when (and process-allocation
(> (+ (allocation-units process-allocation) units)
(resource-units resource)))
(error 'resource-request
"units requested for process exceeds units available")))
(if (> units (resource-units-available resource))
(begin
(let/cc continuation
(let ((new-allocation (allocation process units))
(event (process-event process)))
(set-event-function! event continuation)
(set-event-arguments! event '())
(when reneg
(schedule-event event reneg))
(if (= priority -inf.0)
(queue-insert-last!
(resource-queue resource) new-allocation)
(queue-insert-priority!
(resource-queue resource) new-allocation priority))
((current-simulation-loop-next))))
(let ((process-queue (resource-process-queue resource process)))
(if process-queue
(begin
(queue-remove! (resource-queue resource) process-queue)
#f)
#t)))
(begin
(resource-allocate resource units process)
#t))))
(define resource-relinquish
(case-lambda
((resource units)
(let ((process (current-simulation-process)))
(when (not process)
(error 'resource-relinquish
"no current process"))
(let ((process-allocation (resource-process-allocation resource process)))
(when (not process-allocation)
(error 'resource-relinquish
"attempt to release units when none are allocated"))
(when (> units (allocation-units process-allocation))
(error 'resource-relinquish
"attempt to relase more units than allocated"))
(resource-deallocate resource units process)
(when (not (queue-empty? (resource-queue resource)))
(let ((highest (queue-cell-priority
(queue-first-cell
(resource-queue resource)))))
(let/ec exit
(queue-for-each-cell (resource-queue resource)
(lambda (cell)
(when (or (= (resource-units-available resource) 0)
(and (queue-cell-priority cell)
(< (queue-cell-priority cell) highest)))
(exit))
(let ((allocation (queue-cell-item cell)))
(when (<= (allocation-units allocation)
(resource-units-available resource))
(let ((process (allocation-process allocation)))
(queue-remove-cell!
(resource-queue resource)
cell)
(resource-allocate
resource (allocation-units allocation) process)
(event-list-remove! (process-event process))
(schedule-event (process-event process) '#:now))))))))))
(void)))
((resource)
(let ((process (current-simulation-process)))
(when (not process)
(error 'resource-request
"no current process"))
(let ((process-allocation (resource-process-allocation resource process)))
(when (not process-allocation)
(error 'resource-request
"attempt to release units when none are allocated"))
(resource-relinquish resource (allocation-units process-allocation)))))))
(define-syntax (request stx)
(define-splicing-syntax-class timing
#:attributes (time)
(pattern (~seq #:now) #:with time #''#:now)
(pattern (~seq (~datum now)) #:with time #''#:now)
(pattern (~seq #:at time:expr))
(pattern (~seq ((~datum at) time:expr)))
(pattern (~seq #:in delta:expr) #:with time #'(+ delta (current-simulation-time)))
(pattern (~seq ((~datum in) delta:expr)) #:with time #'(+ delta (current-simulation-time)))
(pattern (~seq #:when event:expr) #:with time #'event)
(pattern (~seq ((~datum when) event:expr)) #:with time #'event)
(pattern (~seq time:expr))
(pattern (~seq) #:with time #'#f))
(syntax-parse stx
((request resource:expr
(~optional (~or (~seq #:units units:expr)
((~seq #:units units:expr)))
#:defaults ((units #'1)))
(~optional (~or (~seq #:priority priority:expr)
((~seq (#:priority priority:expr))))
#:defaults ((priority #'-inf.0)))
reneg:timing)
#'(resource-request resource units priority reneg.time))))
(define-syntax (relinquish stx)
(syntax-parse stx
((relinquish resource:expr
(~or (~seq #:units units:expr)
(~seq (#:units units:expr))))
#'(resource-relinquish resource units))
((relinquish resource:expr)
#'(resource-relinquish resource))))
(define-syntax-rule (with-resource (resource . rest)
body ...)
(begin
(request resource . rest)
body ...
(relinquish resource)))
(define (resource-queue-variable-n resource)
(queue-variable-n (resource-queue resource)))
(define (resource-satisfied-variable-n resource)
(queue-variable-n (resource-satisfied resource)))
(provide (all-defined-out))