~octaspire/cl-octaspire-csfml

6888d27c7c1a73de70aa49b3e446e0322c4d22f8 — octaspire 1 year, 5 months ago 02fa2b5 main
Add initial implementation for memory input stream
6 files changed, 206 insertions(+), 1 deletions(-)

M cl-octaspire-csfml.asd
M doc/API.org
M src/package.lisp
A src/stdlib.lisp
M src/system.lisp
M test/system-test.lisp
M cl-octaspire-csfml.asd => cl-octaspire-csfml.asd +1 -0
@@ 22,6 22,7 @@
    :pathname "src/"
  :serial t
  :components ((:file "package")
               (:file "stdlib")
               (:file "system"))
  :in-order-to ((test-op (test-op :cl-octaspire-csfml/test))))


M doc/API.org => doc/API.org +8 -0
@@ 1,6 1,7 @@
* CSFML
** TODO System
Helper function ~CLAMP~ is available.
Function ~memcpy~ from C standard library is available.
*** DONE Clock.h
**** DONE Function ~sfClock_create~
     Exported as ~CLOCK-CREATE~.


@@ 15,6 16,13 @@ Helper function ~CLAMP~ is available.
*** DONE Export.h
Nothing to do for Lisp side.
*** TODO InputStream.h
**** DONE Structure ~sfInputStream~
     ~sfInputStream~ is defined for Lisp, but is not exported. It has
     four function pointer members: ~MREAD~, ~MSEEK~, ~MTELL~, and ~MGETSIZE~.
     And also pointer member ~MUSERDATA~. Use exported symbol ~:INPUTSTREAM~ instead.
     So instead of writing ~(:STRUCT SFINPUTSTREAM)~, please write ~:INPUTSTREAM~.
     Additional constructor ~MAKE-INPUTSTREAM~ and destructor ~RELEASE-INPUTSTREAM~
     are available. Class ~MEMSTREAM~ can be used to read from memory buffer.
*** DONE Mutex.h
All skipped. Use Lisp instead.
*** DONE Sleep.h

M src/package.lisp => src/package.lisp +10 -1
@@ 21,6 21,8 @@
  (:nicknames :csfml)
  (:use :cl :cffi)
  (:export
   ;; STDLIB
   :memcpy
   ;; SYSTEM
   :vector2i :mx :my
   :make-vector2i :release-vector2i


@@ 60,4 62,11 @@
   :clock-copy
   :clock-destroy
   :clock-getelapsedtime
   :clock-restart))
   :clock-restart
   :inputstream :buffer :buflen :index
     :inputstream-read :inputstream-seek :inputstream-tell :inputstream-getsize
   :memstream
     :make-memstream
   :inputstream :mread :mseek :mtell :mgetsize :muserdata
   :make-inputstream :release-inputstream
   :read-cb :seek-cb :tell-cb :getsize-cb))

A src/stdlib.lisp => src/stdlib.lisp +26 -0
@@ 0,0 1,26 @@
;; Copyright (c) 2022 octaspire.com
;;
;; Permission is hereby granted, free of charge, to any person obtaining a copy
;; of this software and associated documentation files (the "Software"), to deal
;; in the Software without restriction, including without limitation the rights
;; to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
;; copies of the Software, and to permit persons to whom the Software is
;; furnished to do so, subject to the following conditions:

;; The above copyright notice and this permission notice shall be included in all
;; copies or substantial portions of the Software.
;;
;; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
;; IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
;; FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
;; AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
;; LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
;; OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
;; SOFTWARE.
(in-package :cl-octaspire-csfml)

(defcfun ("memcpy" memcpy) :pointer
  "Copy LEN bytes from SRC to DST."
  (dst :pointer)
  (src :pointer)
  (len :size))

M src/system.lisp => src/system.lisp +102 -0
@@ 415,3 415,105 @@ equal to all the rest given times (if only one value is given, returns true)."
;;

;; Skipped. Use Lisp instead.

;;
;; INPUTSTREAM
;;
;; Foreign CSFML types for this section are declared in file
;; SFML/System/InputStream.h.
;;

;; INPUTSTREAM

(defclass inputstream ()
  ((buffer :accessor buffer :initarg :buffer)
   (buflen :accessor buflen :initarg :buflen)
   (index  :accessor index  :initarg :index))
  (:documentation "Base class for input stream implementations."))

(defgeneric inputstream-read (self target amount)
  (:documentation "Read from stream SELF up to AMOUNT octets into TARGET."))

(defgeneric inputstream-seek (self position)
  (:documentation "Try to seek stream SELF to octet index POSITION."))

(defgeneric inputstream-tell (self)
  (:documentation "Return current octet index of stream SELF."))

(defgeneric inputstream-getsize (self)
  (:documentation "Return current octet size of stream SELF."))

(defclass memstream (inputstream)
  ()
  (:documentation "Class for reading input from memory buffer."))

(defun make-memstream (buffer)
  "Constructor for creating and initializing MEMSTREAM."
  (make-instance 'memstream :buffer buffer :buflen (length buffer) :index 0))

(defmethod inputstream-read ((self memstream) target amount)
  (let* ((available (- (buflen self) (index self)))
         (actual (clamp amount 0 available)))
    (with-foreign-objects ((c :char))
      (dotimes (i actual)
        (setf (mem-aref target :char i)
              (aref (buffer self) (index self)))
        (incf (index self)))
      actual)))

(defmethod inputstream-seek ((self memstream) position)
  (setf (index self) (clamp position 0 (1- (buflen self)))))

(defmethod inputstream-tell ((self memstream))
  (index self))

(defmethod inputstream-getsize ((self memstream))
  (buflen self))

(defcallback read-cb :int64
  ((data     :pointer)
   (size     :int64)
   (userdata :pointer))
  (stream-read *userdata* data size))

(defcallback seek-cb :int64
  ((position :int64)
   (userdata :pointer))
  (stream-seek *userdata* position))

(defcallback tell-cb :int64
  ((userdata :pointer))
  (stream-tell *userdata*))

(defcallback getsize-cb :int64
  ((userdata :pointer))
  (stream-getsize *userdata*))

(defcstruct (sfinputstream :class sfinputstream-type)
  "Holds callbacks that allow user to define custom input streams.
Symbol for this type is not exported, please use public type :INPUTSTREAM instead.
So instead of writing (:STRUCT SFINPUTSTREAM), please write :INPUTSTREAM.
Foreign CSFML type is defined in file SFML/System/InputStream.h."
  (mread     :pointer)
  (mseek     :pointer)
  (mtell     :pointer)
  (mgetsize  :pointer)
  (muserdata :pointer))

(defctype :inputstream (:struct sfinputstream)
  "Public type intended to be used instead of (:STRUCT SFINPUTSTREAM).")

(defun make-inputstream (read seek tell getsize userdata)
  "Constructor for allocating and initializing new :INPUTSTREAM."
  (let ((v (foreign-alloc :inputstream)))
    (with-foreign-slots ((mread mseek mtell mgetsize muserdata) v :inputstream)
      (setf mread     read
            mseek     seek
            mtell     tell
            mgetsize  getsize
            muserdata userdata))
    (return-from make-inputstream v)))

(defun release-inputstream (self)
  "Release memory allocated for :INPUTSTREAM SELF."
  (foreign-free self))

M test/system-test.lisp => test/system-test.lisp +59 -0
@@ 676,3 676,62 @@ was expected.")))))
;; THREAD

;; Skipped Use Lisp instead.

;;
;; INPUTSTREAM
;;
;; Foreign CSFML types for this section are declared in file
;; SFML/System/InputStream.h.
;;

;; INPUTSTREAM

(defparameter *userdata* nil)

(fiveam:test
 test-make-inputstream--release-inputstream
 (let* ((read (callback read-cb))
        (seek (callback seek-cb))
        (tell (callback tell-cb))
        (getsize (callback getsize-cb))
        (stream (make-memstream #(1 2 3)))
        (v (make-inputstream read seek tell getsize (null-pointer))))
   (setf *userdata* stream)
   (with-foreign-slots ((mread mseek mtell mgetsize muserdata) v :inputstream)
     (is (pointer-eq read     mread)
         #?"READ component is $(mread) when $(read) was expected.")
     (is (pointer-eq seek     mseek)
         #?"SEEK component is $(mseek) when $(seek) was expected.")
     (is (pointer-eq tell     mtell)
         #?"TELL component is $(mtell) when $(tell) was expected.")
     (is (pointer-eq getsize  mgetsize)
         #?"GETSIZE component is $(mgetsize) when $(getsize) was expected."))
   (is (= 3 (inputstream-getsize stream))
       (format nil
               "INPUTSTREAM-GETSIZE returned ~A, when 3 was expected."
               (inputstream-getsize stream)))
   (is (= 0 (inputstream-tell stream))
       (format nil
               "INPUTSTREAM-TELL returned ~A, when 0 was expected."
               (inputstream-tell stream)))
   (with-foreign-object (target :char 1)
     (let ((octets-read (inputstream-read stream target 1))
           (value-saved (mem-aref target :char 0)))
       (is (= 1 octets-read) #?"One octet was expected, but got $(octets-read)")
       (is (= 1 value-saved) #?"Octet was $(value-saved), when 1 was expected.")))
   (is (= 1 (inputstream-tell stream))
       (format nil
               "INPUTSTREAM-TELL returned ~A, when 1 was expected."
               (inputstream-tell stream)))
   (with-foreign-object (target :char 2)
     (let ((octets-read (inputstream-read stream target 2))
           (value-saved-1 (mem-aref target :char 0))
           (value-saved-2 (mem-aref target :char 1)))
       (is (= 2 octets-read)   #?"Two octets were expected, but got $(octets-read)")
       (is (= 2 value-saved-1) #?"First octet was $(value-saved-1), when 2 was expected.")
       (is (= 3 value-saved-2) #?"Second octet was $(value-saved-2), when 3 was expected.")))
   (is (= 3 (inputstream-tell stream))
       (format nil
               "INPUTSTREAM-TELL returned ~A, when 3 was expected."
               (inputstream-tell stream)))
   (release-inputstream v)))