-
Notifications
You must be signed in to change notification settings - Fork 161
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Plugin API: a proper File Stream API #2227
Plugin API: a proper File Stream API #2227
Conversation
Do we need all iagsstream (or at all) or can we make a smaller part of it, so we have more flexibility? I am just trying to figure what is really needed, because once this is external, it has to be kept in that API - I remember you were thinking about some change to streams. Edit: now I see the GitHub issue refers to a different IAGSStream and not IAGSStream.h, so I may be making a confusion. |
Okay, I made a successful test with plugin, and made theora video run on Android through this interface, being streamed by the engine:
It's all under question. Years ago I've been thinking to make Stream base class to implement plugin interface directly. That's an option. But existing IAGSStream is way too cumbersome, there's no need for that. In regards to change to streams, here's an approximate way of how this may be done: |
ced66a5
to
7105e8d
Compare
Added IAGSStream variant of stream API, updated PR description. |
So, to clarify on implementation, I am making our base Stream class to implement an interface (contract) identical to the one exposed in plugin API, because it already matches the requirements, and pass this stream object to plugins. But this does not impose any restrictions on any future changes to these classes. If this becomes inconvenient at any point to pass Stream object to plugins directly, then we may as well switch to another class, or write a wrapper over Stream. It is all good so long as the returned object implements exact interface. I also added two standard C headers to the plugin api header: stddef.h and stdint.h. This is for int64_t and size_t. I believe these are pretty common today, so there's no need to declare our custom types there. In the worst case, we might add a preprocessor switch there and define these types (or ask plugin authors to include such declaration prior to plugin header). I still have doubts about size_t, and whether it is okay to have it in plugin API. As this type's size depends on build configuration. It may be argued that this type's size should be in sync with pointer size (32-bit, 64-bit etc), and then it will only be required to have plugin build match engine build; but this may not be always true (?), and I wonder if it also may depend on compiler and standard library implementation. EDIT: did a quick check on SDL, and their rwops use size_t of course (rwops may be a good reference for the stream api): In terms of API, my biggest doubt right now is stream closing. I did it with a function in IAGSEngine interface for the test, but it may be more convenient to have a Close method in IAGSStream instead. Problem is that such method must also deallocate the stream object and possibly do something else in the engine, if engine wants to track streams opened for plugins, for example. That means this cannot be done with pure Stream object, there got to be a wrapper instead. Why it's more convenient to have Close in IAGSStream: because if we want to support streams provided by plugins too at some point, it will be safer to just call Close instead of remembering which plugin this stream came from and call its "CloseStream" function. |
53e605a
to
3f681ca
Compare
Alright, updated once more. Please see PR's description for API details. In summary, now the IAGSStream closes itself with Close method, which is supposed to deallocate the stream object (so pointer becomes invalid after this). Also added Flush, EOS and GetPath() methods, for sake of completeness. I think that everything else may be done by wrapping or combining existing methods. I had to make a wrapper over our Stream object for this to work. I'm testing this using my plugin here in this temp branch, where it utilizes this interface to read video: BTW, with this one can read video or image files packaged inside *.ags pack too (like ogvs normally are packaged in ags games). |
3f681ca
to
ce0dd88
Compare
...forgot to update api version. |
Since this was added in recent Beta, and we include size_t definition into plugin api now.
I am a bit lost on one specific API
What is the use-case here? I am trying to figure what event callback this is meant to work with. |
Right now there are 2 events that pass int32 "file handler" into plugins: In the future we might redesign how the events work, maybe even provide more specific prototypes for each event (and deprecate GetFileStreamByHandle). But for the time being, this is a workaround: pass a int32 filehandle and let plugin get IAGSStream from it if it needs to. |
That is fine. The new api looks ok API wise when comparing with the rest of the current API. My minor nitpick would be the following
Origin here is not a position, it's one of the macros that work like enums here - I guess for some C reason. But the macro doesn't mention they are related to origin.
I would suggest renaming origin to origin_type and mention it in the macro.
It's verbose but it makes it easier to understand (I think). |
No, sorry, I do not want to do this. I've been already struggling to keep these macro names short. It's one thing when the macro is used as a global setting that is set once per program (like SDL hints), but this is a common operation. We may plan to rework plugin API for ags4, put everything in a namespace and turn macros to enums/enum classes (for compilers that support them). That may shorten typing things too. |
The issue is the comment is "Seeks to offset from the origin" and origin being an int, it's hard to understand that origin is a small set of possibilities - and not some other offset. I would suggest at least rewording the comment to "Seeks to offset from the beginning, current position or end of stream". |
I tried this:
|
Looks good! Thanks, this is easier to understand! :) |
Also made Seek return new position instead of bool, following examples from SDL and .NET framework. |
It turns out our Stream's Seek return value is not defined, and not documented either. But judging by the other Stream implementations, it was supposed to return positive whenever there's NO ERROR, regardless of whether the position is matching user's request. I suppose we may also make Seek return the resulting position, similar to how some other libs do (e.g. see SDL and .NET Framework).
This may be more convenient if user wants to tell where did it end, rather than calling GetPosition immediately after. NOTE: current implementation *does* call Seek + GetPosition, but that's because our own utility Stream::Seek returns simple bool. We may adjust this later.
9678a16
to
646c8cd
Compare
I have seconds thoughts about this, or rather the way it is done, following my experiments with #2256 (and others). The problem with the current implementation is that the stream interface exposed to plugins is a different type from our common stream interface. When giving out stream to plugins this requires a wrapper, which is not very nice but tolerable. However, there may also be an opposite situation, where the stream object will be passed from plugin to engine. I see two hypothetical cases when this may happen:
In this situation, imo, it would be convenient if the stream interface would directly match internal engine's stream interface. If it does not, then there will be extra code branching, and maybe additional need for wrappers whenever such stream is used. When I tried matching interfaces at first while writing this PR, I met 2 problems:
Problem 1 turned out to be mostly a non-issue, as "extended" types such as size_t and int64_t may be allowed in plugin api. The only remaining inconvenience is a method GetPath that returns a
Problem 2 is a more complicated one. There are 2 classic approaches for the plugin interface:
The second approach, although technically possible, will be inconsistent with our stream class, and generally with common-use C++ classes which objects may be allocated on a stack. Hence this is unsuitable if implemented directly in Stream, unless we make a rule to never allocate Stream object on a stack, and possibly enforce this with some trick in code... The first approach seems easy at first: plugin opens a stream using IAGSEngine::OpenFileStream and destroys it using IAGSEngine::FreeFileStream (or similar name). The issue becomes somewhat more complicated in case the IAGSStream pointer came from a plugin, and depending on how it is done. It's one thing when plugin provides a sort of a "Stream Factory" interface, which also may be stored along with the stream ptr. But there easily may be a situation when engine won't know where the stream ptr came from. The example of such situation is given by me above:
So this is a blocking issue in the first approach (create/destroy factory functions). This puts me in some heavy doubts. May be I'm missing something obvious, or a tolerable workaround. I'd like to spend some more time to think about this... |
After thinking this over for a while, I can see only two potential solutions for the problem of "dispose" method, that would allow to avoid using extra wrappers. This refers to any objects shared between engine and plugin, that may be freely deleted: stream, audio and video processors (decoders, players, filters), and anything of that kind. These solutions are:
So when the stream, or any other disposable object is passed from plugin to engine, it's done using this struct instead, for example:
The struct itself is not owned, and should not be deleted by the engine, of course. We simply take values out from it. |
For #2226.
This is a draft made primarily for testing these changes on Android.Done in a more primitive way first, without a IAGSStream struct, but added F* functions (FSeek, FLength etc) into IAGSEngine instead. Plugin works with stream using int32 handles. This is a working variant, although an "ugly" one.UPDATE: added a IAGSStream interface to plugin API.
Overall changes to plugin API look like this: