Monday, August 6, 2012

Little summary

Now, after two months of work, it is time to show progress and describe the architecture of events recorder. This is a class diagramm, which can give some understanding of how does events recorder relates with other classes of scummvm.

Lets describe it particulary by means of subsistems, which it records. 
User Input: to record events from mouse and keyboard, eventRecorder implements DefaultEventMapper. During the initialisation, it registers itself as a mapper. So, every event occurring in system, is catched with EventRecorder's pollEvent function. This function checks state of the recorder and calls PlaybackFile's writeEvent() function to write this event in temporary memory buffer. If the buffer is full, playback file dumps it to file and takes screenshot, calculates checksumm and dumps it to the file too.
To playback events, recorder does everything in reverse order. It implements EventSource and reguster in EventDispatcher as the source of events. Every time when game engine calls pollEvent function, EventDispatcher calls pollEvent function of EventRecorder. This function checks if the EventRecorder in playback mode and calls getNextEvent function of PlaybackFile class. This function read event from events memory buffer, and if buffer is empty, it reads next events chunk from the file. Also it reads screenshot from the file and compare it with current screen image. If there are any differences, it shows warning message. 
System time:   To correct playback process we need to write and to play system time when a game engines queried for it. To do this, we inserted processMillis call to OSystem_SDL::getMillis function. This function in recording mode writes system time to the file and in playback mode reads time from the file and give it to  a game. The problem is that this meathod registers time queries not only from a game engine, but from GUI and TimerManager. It ruins playback, cause gui and timer manager not synchronised with engine logic. To solve this problem, two improvements to the code were made. The first one -  parameter which disallow to register this query was added to getMillis function. By default, this parameter sets to false. It sets to true in calls from GUI. The second one - call timers update routine from EventRecorder/ became possible.
Audio: As audio fragments are played in other thread and controlled only with SDL subsystem, there could be some problems with  audio flow synchronization and user events. For instance, in Kyrandia, when you click anywhere while somebody's talking, sound interrupts and mouse event doesn't pass to the game. In the same situation, durring playback, sound may be played a little bit faster. And mouse's event will be passed to the game and it's possible that the whole gameplay will chrash. To avoid this unpleasant situation, new class had been added: NullSdlMixerManager. It updates with EventsRecorder and "output" sound to nowhere. And we may always know that during playback sound ends the same time it ends during recording.
Random numbers: as an engine use pseudo random sequences, and we can prognose the whole sequence by initial value of random numbers generator, it's very easy to record and playback random events. It's necessary only to hold this initial value and restore it. Every time RandomSource is created, it calls getRandomSeed() function of EventRecorder. If the recorder not in recording or playback mode, this function works just as bypass.In playback mode it changes initial value to recorded one.

Friday, July 27, 2012

What has been done on this week

Hi. This week I've add new features to events recorder. Now it can replay game without display it on screen:  it initialize memory buffer instead of initializing of SDL context. Next feature: user can switch recording mode to normal game play. I. e.while user is recording his gameplay he may interrupt it and continue playig in normal mode. You may see on this diagram how it works:

Friday, July 20, 2012

Finished save files embedding

On this week I've finished savefiles embedding. Following the fuzzie's idea, I created class which inherits DefaultSaveFileManager and implements listFiles and openForLoading functions. During the recording or playback it calls functions of events recorder. This functions read hold or read save files from recording file.

Also, I've begin work on onsreen recording dialog. This dialog will show during the recording and playback and allow to control the recording process. You may see very first version of this dialog on following video: 

Friday, July 13, 2012

Refactoring week

So, I'm still working on integration of save files into recording file. On last week I did alot of things to this. After code studying, I saw that every engine supporting states loading, have some kind of fucntion which generates file name by slot's number. Some engines have this function as is a class static method, others like public method, few engines have this function inlined to code of saves-related functions. In other words, we have same behavior and different interface. So after discussion with mentor we decided to unificete interface of save files name generation and make all engines compatible with this interface. Then we produced some requirements to name generation function:

1) It should be accessible without class instancing. Because MetaEngine use it for removeSaveState and querySaveMetaInfo functions.
2) It should be accessible inside of class methods (obviously)
3) It should be accessible outside of class using the pointer of abstract class Engine. It to make possible to use it from eventRecorder
4) Logic of name generation should be in one place. Because it's possible to get error relate to duplicated code. For instance, in current version of queen engine, function QueenEngine::makeGameStateName uses constant string "queen" and function QueenMetaEngine::removeSaveState uses variable target for name generation. This may cause a problems.
5) Preferably to have a fucntion which check number of saving's slot is special (for ecample used for autosave or recording file) and generate name without depending to engine implementation

As most engines generate file name by formula fileName = preffix + "." + pattern, where preffix is a name of engine and pattern is function from slot number (for example "%03d"). Firstly I thought that it would be a nice idea to make a universal method in Engine class. Parameters of this function would be preffix (usualy target name) and pattern to generate extension of save file. This method could look like Engine::getSaveName(prefix, pattern, slot). But some engine need different algorithm of names generation and each time we call this method we should pass to many params to it, which is not very comfortable and can be cause of errors.

Then I decided that it should be good to make and AbstractNameGenerator class with abstract generateSaveName method, and inherited it for each engine. You can see it on diagram:

I would be consistent to all requirements, but create class without state which contains only one method is definitely overengineerng.
So, we decided to make static method and wrap it by virtual function in each engine. And in every virtual function call checking for special slot.

And then fuzzie suggested to  wrap the SaveFileManager's openForLoading function, for the event recorder. And this idea looks very attractive, cause at first, it can be realized without changing of existing codem and at second it corresponds to the logic of other eventrecorder's systems. So at the moment I'm working on implementation this idea

Friday, July 6, 2012

Savefiles embedding

It's needed to make a recording self-sufficient and independent from save files. At the moment, user should  make following sequence of actions to playback loaded game:
1) choose game
2) select playback record
3) press "Playback" button
4) in pop up dialog box select is it need to load from savegame.
5) if loading from savegame choosed, show load game dialog

After I implement this feature, 4th and 5th steps will be removed. Also, will be sufficient only recording file to playback the gameplay. Will not be necessary to share save file.

At first glance, it's easy to implement this feature: just current save file during the recording and give it to
engine during the playback. But it's not so easy to do, using the current engine API.

First of all, every function related to saving and loading works only with slot numbers and save files name generation is different for each engine, so our function can't get filename of current save. I've tried to solve this by adding the virtual funtion getSaveFileName to engine class. Anyway, this function is in every engine in different forms: somewhere it part of loading function, somewhere it's separate function, but it exists in any engine. So, i think it's a good idea to make an unificated interface for it.

Next issue is to load savefile from recording. As I said above,  we can load savefiles only by their slot number. There was to  ways to solve it: write procedure which loads game state from given stream or create some virtual slot used only for recordings. First way requires to large code rewriting, so we chosen second.
I've almost implemented it, and now I'm doing testing and fixing a bugs. Hope to show a demo soon.

Friday, June 29, 2012

Nothing special

This week I've just did little code refactoring and moved all playback file related issues to separate class. Also, following my mentor's advice, I implemented cache for recording dialog. Now It reads every file header before adds it to list, store file's header to array and every time when user update selected record, it doesn't need to reopen the file.
What I want to implement now it's to keep the game save in playback file, i.e. if we load file before recording, it shouldn't ask what record you want to load. It should load it automatically. I'm not sure is it possible, but it may be great feature.

Tuesday, June 26, 2012

GUI feautures of event recorder

Last week I've added opportunity to view playback file screenshots directly from GUI. How it works you may see on the following video:

Also I've added dialog window to edit info about author, visible name and description of playback file.

During the work by GUI we've desided to move all operation with file to separate class. It should provide high flexibility and make possible to simple access to playback file. So I've added class PlaybackFile which works with file and provide higher level of abstraction.