;;; guile-2d ;;; Copyright (C) 2013 David Thompson ;;; ;;; Guile-2d is free software: you can redistribute it and/or modify it ;;; under the terms of the GNU Lesser General Public License as ;;; published by the Free Software Foundation, either version 3 of the ;;; License, or (at your option) any later version. ;;; ;;; Guile-2d is distributed in the hope that it will be useful, but ;;; WITHOUT ANY WARRANTY; without even the implied warranty of ;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ;;; Lesser General Public License for more details. ;;; ;;; You should have received a copy of the GNU Lesser General Public ;;; License along with this program. If not, see ;;; . ;;; Commentary: ;; ;; Deferred procedure scheduling. ;; ;;; Code: (define-module (2d agenda) #:use-module (ice-9 q) #:use-module (srfi srfi-1) #:use-module (srfi srfi-9) #:use-module (srfi srfi-26) #:use-module (2d coroutine) #:export (make-agenda agenda? agenda-time tick-agenda! clear-agenda! schedule schedule-interval schedule-each wait)) ;; This code is a modified version of the agenda implementation in ;; SICP. Thank you, SICP! ;;; ;;; Time segment ;;; (define-record-type (%make-time-segment time queue) time-segment? (time segment-time) (queue segment-queue)) (define (make-time-segment time . callbacks) "Create a new time segment at TIME and enqueues everything in the list CALLBACKS." (let ((segment (%make-time-segment time (make-q)))) ;; Enqueue all callbacks (for-each (cut segment-enq segment <>) callbacks) segment)) (define (segment-enq segment callback) "Add the CALLBACK procedure to SEGMENT's queue." (enq! (segment-queue segment) callback)) ;;; ;;; Agenda ;;; (define-record-type (%make-agenda time segments) agenda? (time agenda-time set-agenda-time!) (segments agenda-segments set-agenda-segments!)) (define (make-agenda) "Create a new, empty agenda." (%make-agenda 0 '())) (define (agenda-empty? agenda) "Return #t if AGENDA has no scheduled procedures." (null? (agenda-segments agenda))) (define (first-segment agenda) "Return the first time segment in AGENDA." (car (agenda-segments agenda))) (define (rest-segments agenda) "Return everything but the first segment in AGENDA." (cdr (agenda-segments agenda))) (define (agenda-add-segment agenda time callback) "Add a new time segment to the beginning of AGENDA at the given TIME and enqueue CALLBACK." (set-agenda-segments! agenda (cons (make-time-segment time callback) (agenda-segments agenda)))) (define (insert-segment segments time callback) "Insert a new time segment after the first segment in SEGMENTS." (set-cdr! segments (cons (make-time-segment time callback) (cdr segments)))) (define (first-agenda-item agenda) "Return the first time segment queue in AGENDA." (if (agenda-empty? agenda) (error "Agenda is empty") (segment-queue (first-segment agenda)))) (define (agenda-time-delay agenda dt) "Return the sum of the time delta, DT, and the current time of AGENDA." (+ (agenda-time agenda) (inexact->exact (round dt)))) (define (flush-queue! q) "Dequeue and execute every member of Q." (unless (q-empty? q) ((deq! q)) ;; Execute scheduled procedure (flush-queue! q))) (define (tick-agenda! agenda) "Move AGENDA forward in time and run scheduled procedures." (set-agenda-time! agenda (1+ (agenda-time agenda))) (let next-segment () (unless (agenda-empty? agenda) (let ((segment (first-segment agenda))) ;; Process time segment if it is scheduled before or at the ;; current agenda time. (when (>= (agenda-time agenda) (segment-time segment)) (flush-queue! (segment-queue segment)) (set-agenda-segments! agenda (rest-segments agenda)) (next-segment)))))) (define (clear-agenda! agenda) "Remove all scheduled procedures from AGENDA." (set-agenda-segments! agenda '())) (define* (schedule agenda thunk #:optional (delay 1)) "Schedule the procedure THUNK in AGENDA to be run DELAY ticks from now. DELAY defaults to 1 if not specified." (let ((time (agenda-time-delay agenda delay))) (define (belongs-before? segments) (or (null? segments) (< time (segment-time (car segments))))) (define (add-to-segments segments) ;; Add to existing time segment if the times match (if (= (segment-time (car segments)) time) (segment-enq (car segments) thunk) ;; Continue searching (if (belongs-before? (cdr segments)) ;; Create new time segment and insert it where it belongs (insert-segment segments time thunk) ;; Continue searching (add-to-segments (cdr segments))))) (if (belongs-before? (agenda-segments agenda)) (agenda-add-segment agenda time thunk) (add-to-segments (agenda-segments agenda))))) (define (schedule-interval agenda thunk delay) "Schedule THUNK within AGENDA to be applied every DELAY ticks." (schedule agenda (lambda () (thunk) (schedule-interval agenda thunk delay)) delay)) (define (schedule-each agenda thunk) "Schedule THUNK within AGENDA to be applied every tick." (schedule-interval agenda thunk 1)) (define (wait agenda delay) "Yield coroutine and schedule the continuation to be run after DELAY ticks." (yield (cut schedule agenda <> delay)))