1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
#![allow(non_camel_case_types)]
#![allow(non_upper_case_globals)]
use std::convert::TryInto;
use std::ops::{Deref, DerefMut};
use std::os::raw::{c_char, c_int, c_void};

use crate::types::{frefid_t, schanid_t, strid_t, winid_t};
use crate::util;

pub use glk_sys::gidispatch_rock_t as rock;

/* Array type codes. The only ones ever needed for the current Glk version. These are
 * represented as zero-terminated byte slices to be able to pass them to C without the need for
 * conversion.
 */
/** u8 array type code. */
pub const TYPECODE_U8_ARRAY: &[u8] = b"&+#!Cn\0";
/** u32 array type code */
pub const TYPECODE_U32_ARRAY: &[u8] = b"&+#!Iu\0";

/* Types for gidispatch API */
pub type objref = *mut c_void;
pub type ObjRegiFunc = unsafe extern "C" fn(obj: objref, objclass: u32) -> rock;
pub type ObjUnregiFunc = unsafe extern "C" fn(obj: objref, objclass: u32, objrock: rock);
pub type RetainedRegiFunc =
    unsafe extern "C" fn(array: *mut c_void, len: u32, typecode: *const c_char) -> rock;
pub type RetainedUnregiFunc =
    unsafe extern "C" fn(array: *mut c_void, len: u32, typecode: *const c_char, objrock: rock);
pub type AutoLocateFunc = unsafe extern "C" fn(
    array: *mut c_void,
    len: u32,
    typecode: *const c_char,
    objrock: rock,
    elemsizeref: *mut c_int,
) -> ::std::os::raw::c_long;
pub type AutoRestoreFunc = unsafe extern "C" fn(
    bufkey: ::std::os::raw::c_long,
    len: u32,
    typecode: *const c_char,
    arrayref: *mut *mut c_void,
) -> rock;

/** Dummy (zero) rock value */
pub const DUMMY_ROCK: rock = rock { num: 0 };

/** GI dispatch handler functions. See
 * https://www.eblong.com/zarf/glk/glk-spec-075_12.html#s.1.5.1 and
 */
pub trait Handlers {
    /** The object registry holds an opaque "rock" value for every
     * (obj, objclass) pair. This is used to keep track of internal identifiers
     * of the VM for objects. These can be requested back using
     * `gidispatch_get_objrock`. It is required to implement these.
     *
     * - "regi" should be called for new objects created by the Glk implementation, returning the
     * gidisp rock to be stored with the object.
     * - "unregi" should be called before an object is deleted to clean up the rock.
     */
    fn gidispatch_set_object_registry(&mut self, _registry: ObjRegistry);

    /** Request back the previously stored "dispatch rock" for an object.
     * Note: glulxe will turn it into a pointer and dereference this without checking,
     * so leaving this unimplemented will crash.
     */
    fn gidispatch_get_objrock(&mut self, _obj: objref, _objclass: u32) -> rock;

    /**
     * Sets the "retained registry". This is a set of functions like the object registry,
     * but meant to be called for arrays that the Glk implementation holds on to.
     * It is required to implement these for the functions to work.
     *
     * These is used for the following:
     *   - glk_stream_open_memory[_uni]
     *   - glk_request_line_event[_uni]
     */
    fn gidispatch_set_retained_registry(&mut self, _registry: RetainedRegistry);

    /** Registers handlers for saving/restoring the interpreter state
     * without cooporation of the game, this is used on mobile platforms where
     * the application might have to go to the background unexpectedly.
     *
     * - `locatearr` needs to be called for every retained array on autosave; it is passed the
     * array address (as the Glk library knows it), length, typecode and dispatch rock. It returns
     * a bufkey (opaque C long) and a element size that need to be stored with the array.
     * - `restorearr` needs to be called for every retained array on autorestore; it is passed the
     * bufkey (returned by `locatearr`), length, typecode, and restores the array
     * address and dispatch rock.
     *
     * @note Implementing this is optional: it only needs to be implemented if the interpreter does
     * autosave and autorestore.
     */
    fn gidispatch_set_autorestore_registry(
        &mut self,
        _locatearr: Option<AutoLocateFunc>,
        _restorearr: Option<AutoRestoreFunc>,
    ) {
        // Default implementation that does nothing.
    }
}

/** Newtype for gidisp_class: Glk opaque handle namespaces */
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
pub struct class(pub u32);
impl class {
    pub const Window: Self = Self(0);
    pub const Stream: Self = Self(1);
    pub const Fileref: Self = Self(2);
    pub const Schannel: Self = Self(3);
}

/** Trait so that functions can accept opaque handles {winid_t,strid_t,frefid_t,schanid_t} and
 * convert them to a generic reference and class pair.
 */
pub trait ObjRef {
    const OBJCLASS: class;
    fn as_objref(&self) -> objref;
}

impl ObjRef for winid_t {
    const OBJCLASS: class = class::Window;
    fn as_objref(&self) -> objref {
        (*self) as objref
    }
}

impl ObjRef for strid_t {
    const OBJCLASS: class = class::Stream;
    fn as_objref(&self) -> objref {
        (*self) as objref
    }
}

impl ObjRef for frefid_t {
    const OBJCLASS: class = class::Fileref;
    fn as_objref(&self) -> objref {
        (*self) as objref
    }
}

impl ObjRef for schanid_t {
    const OBJCLASS: class = class::Schannel;
    fn as_objref(&self) -> objref {
        (*self) as objref
    }
}

/** Safe-ish wrapper to call object registry callbacks.
 */
#[derive(Copy, Clone)]
pub struct ObjRegistry {
    regi: Option<ObjRegiFunc>,
    unregi: Option<ObjUnregiFunc>,
}

impl ObjRegistry {
    pub fn new() -> Self {
        Self {
            regi: None,
            unregi: None,
        }
    }
    pub fn new_with_funcs(regi: Option<ObjRegiFunc>, unregi: Option<ObjUnregiFunc>) -> Self {
        Self { regi, unregi }
    }
    pub fn register<T: ObjRef>(&self, id: T) -> rock {
        if let Some(func) = self.regi {
            unsafe { (func)(id.as_objref(), T::OBJCLASS.0) }
        } else {
            DUMMY_ROCK
        }
    }
    pub fn unregister<T: ObjRef>(&self, id: T, rock: rock) {
        if let Some(func) = self.unregi {
            unsafe { (func)(id.as_objref(), T::OBJCLASS.0, rock) }
        }
    }
}

/** Safe-ish wrapper to call retained registry callbacks.
 */
#[derive(Copy, Clone)]
pub struct RetainedRegistry {
    regi: Option<RetainedRegiFunc>,
    unregi: Option<RetainedUnregiFunc>,
}

impl RetainedRegistry {
    pub fn new() -> Self {
        Self {
            regi: None,
            unregi: None,
        }
    }
    pub fn new_with_funcs(
        regi: Option<RetainedRegiFunc>,
        unregi: Option<RetainedUnregiFunc>,
    ) -> Self {
        Self { regi, unregi }
    }
    /** Use typestate to keep track of retained objects.
     */
    pub fn register<T>(&self, array: &mut RetainableBuffer<T>) -> RetainedBuffer<T> {
        let rock = if let Some(func) = self.regi {
            if array.len != 0 {
                // Zero-length arrays don't need registration, nor unregistration.
                unsafe {
                    (func)(
                        array.array as *mut c_void,
                        array.len.try_into().unwrap(),
                        array.typecode.as_ptr() as *const c_char,
                    )
                }
            } else {
                DUMMY_ROCK
            }
        } else {
            DUMMY_ROCK
        };
        RetainedBuffer::<T>::new(array.array, array.len, rock, array.typecode, self.unregi)
    }
    /* unregister happens in in RetainedBuffer drop */
}

/** A retainable buffer on the Glk API. This is an opaque object
 * that cannot be used without retaining it through a RetainedRegistry.
 */
pub struct RetainableBuffer<T> {
    array: *mut T,
    len: usize,
    typecode: &'static [u8],
}
impl<T> RetainableBuffer<T> {
    pub fn new(array: *mut T, len: usize, typecode: &'static [u8]) -> Self {
        Self {
            array,
            len,
            typecode,
        }
    }
}

/** A Glk retained buffer. When dropped it will be returned to the Glk client. */
pub struct RetainedBuffer<T> {
    array: *mut T,
    len: usize,
    rock: rock,
    typecode: &'static [u8],
    unregi: Option<RetainedUnregiFunc>,
}
impl<T> RetainedBuffer<T> {
    pub fn new(
        array: *mut T,
        len: usize,
        rock: rock,
        typecode: &'static [u8],
        unregi: Option<RetainedUnregiFunc>,
    ) -> Self {
        Self {
            array,
            len,
            rock,
            typecode,
            unregi,
        }
    }
    /** Return the buffer as a slice of T. */
    pub fn as_slice(&self) -> &[T] {
        unsafe { util::from_raw_parts(self.array, self.len) }
    }
    /** Return the buffer as a mutable slice of T. */
    pub fn as_mut_slice(&mut self) -> &mut [T] {
        unsafe { util::from_raw_parts_mut(self.array, self.len) }
    }
}
impl<T> Deref for RetainedBuffer<T> {
    type Target = [T];
    fn deref(&self) -> &[T] {
        self.as_slice()
    }
}
impl<T> DerefMut for RetainedBuffer<T> {
    fn deref_mut(&mut self) -> &mut [T] {
        self.as_mut_slice()
    }
}
impl<T> Drop for RetainedBuffer<T> {
    fn drop(&mut self) {
        if self.len != 0 {
            if let Some(func) = self.unregi {
                unsafe {
                    (func)(
                        self.array as *mut c_void,
                        self.len.try_into().unwrap(),
                        self.typecode.as_ptr() as *const c_char,
                        self.rock,
                    )
                }
            }
        }
    }
}