1const std = @import("../std.zig");
2const builtin = @import("builtin");
3const assert = std.debug.assert;
4const testing = std.testing;
5const Lock = std.event.Lock;
6
7/// This is a value that starts out unavailable, until resolve() is called
8/// While it is unavailable, functions suspend when they try to get() it,
9/// and then are resumed when resolve() is called.
10/// At this point the value remains forever available, and another resolve() is not allowed.
11pub fn Future(comptime T: type) type {
12    return struct {
13        lock: Lock,
14        data: T,
15        available: Available,
16
17        const Available = enum(u8) {
18            NotStarted,
19            Started,
20            Finished,
21        };
22
23        const Self = @This();
24        const Queue = std.atomic.Queue(anyframe);
25
26        pub fn init() Self {
27            return Self{
28                .lock = Lock.initLocked(),
29                .available = .NotStarted,
30                .data = undefined,
31            };
32        }
33
34        /// Obtain the value. If it's not available, wait until it becomes
35        /// available.
36        /// Thread-safe.
37        pub fn get(self: *Self) callconv(.Async) *T {
38            if (@atomicLoad(Available, &self.available, .SeqCst) == .Finished) {
39                return &self.data;
40            }
41            const held = self.lock.acquire();
42            held.release();
43
44            return &self.data;
45        }
46
47        /// Gets the data without waiting for it. If it's available, a pointer is
48        /// returned. Otherwise, null is returned.
49        pub fn getOrNull(self: *Self) ?*T {
50            if (@atomicLoad(Available, &self.available, .SeqCst) == .Finished) {
51                return &self.data;
52            } else {
53                return null;
54            }
55        }
56
57        /// If someone else has started working on the data, wait for them to complete
58        /// and return a pointer to the data. Otherwise, return null, and the caller
59        /// should start working on the data.
60        /// It's not required to call start() before resolve() but it can be useful since
61        /// this method is thread-safe.
62        pub fn start(self: *Self) callconv(.Async) ?*T {
63            const state = @cmpxchgStrong(Available, &self.available, .NotStarted, .Started, .SeqCst, .SeqCst) orelse return null;
64            switch (state) {
65                .Started => {
66                    const held = self.lock.acquire();
67                    held.release();
68                    return &self.data;
69                },
70                .Finished => return &self.data,
71                else => unreachable,
72            }
73        }
74
75        /// Make the data become available. May be called only once.
76        /// Before calling this, modify the `data` property.
77        pub fn resolve(self: *Self) void {
78            const prev = @atomicRmw(Available, &self.available, .Xchg, .Finished, .SeqCst);
79            assert(prev != .Finished); // resolve() called twice
80            Lock.Held.release(Lock.Held{ .lock = &self.lock });
81        }
82    };
83}
84
85test "std.event.Future" {
86    // https://github.com/ziglang/zig/issues/1908
87    if (builtin.single_threaded) return error.SkipZigTest;
88    // https://github.com/ziglang/zig/issues/3251
89    if (builtin.os.tag == .freebsd) return error.SkipZigTest;
90    // TODO provide a way to run tests in evented I/O mode
91    if (!std.io.is_async) return error.SkipZigTest;
92
93    testFuture();
94}
95
96fn testFuture() void {
97    var future = Future(i32).init();
98
99    var a = async waitOnFuture(&future);
100    var b = async waitOnFuture(&future);
101    resolveFuture(&future);
102
103    const result = (await a) + (await b);
104
105    try testing.expect(result == 12);
106}
107
108fn waitOnFuture(future: *Future(i32)) i32 {
109    return future.get().*;
110}
111
112fn resolveFuture(future: *Future(i32)) void {
113    future.data = 6;
114    future.resolve();
115}
116