libsgfc++ 2.0.1
A C++ library that uses SGFC to read and write SGF (Smart Game Format) data.
|
The library has been designed with a strict separation of the library's public API from the library's private implementation.
All public types are one of the following:
interface
keyword.static
-declared factory class SgfcPlusPlusFactory
.The library-internal types that provide the implementations for the public interfaces are marked with a declaration that explicitly sets symbol visibility of these types to "hidden". The goal is that the library client never instantiates library objects on its own, but instead uses the top-level factory class SgfcPlusPlusFactory
to do so.
The library attempts to protect the library client from having to perform any sort of memory management by making use of std::shared_ptr
. This smart pointer type has been introduced to the C++ standard in C++11, and if you don't know it yet then you should be quick to learn more about it.
The goal is, essentially, that the library client can pass around references to library objects without having to constantly think about pointer/reference mechanics and who is responsible for destroying those objects. For example, after the library client has read an SGF file and has obtained a reference to the resulting ISgfcDocument
object, the entire object tree that represents the game trees in the SGF file can be safely passed around using the ISgfcDocument
reference. Once the library client removes the last reference to the ISgfcDocument
object, the entire object tree will be safely deallocated.
In a few places where it is necessary to break reference cycles the library uses std::weak_ptr
instead of std::shared_ptr
.
A number of interfaces contain convenience methods that help with downcasting the interface type to a more specific sub-type. These methods are named To...()
and return a raw pointer without an encapsulating std::shared_ptr
. The caller of any these methods is not the owner of the returned object and must never destroy the returned object. Example: ToNumberValue()
and many other similar methods in ISgfcSinglePropertyValue
.
Using the factories concurrently is safe.
A library client can also safely perform concurrent load or save operations using different document reader/writer objects and document object trees. The actual SGFC backend operations are serialized by libsgfc++, though, because SGFC uses a single global hook/callback function for sending message data back to libsgfc++. Serializing the operations makes sure that an operation on thread A does not receive messages generated by an operaton on thread B, or vice versa. Serialization is implemented in the SgfcBackendController
class using a central std::mutex
.
No other precautions have been taken to make libsgfc++ thread-safe. If the library client wants to operate on the same document object tree in different threads then it must make sure that access is properly synchronized.
The library has abstracted SGFC's functionality into 3 interfaces which can be seen to represent the library's main operation modes:
ISgfcCommandLine
. In this mode the library mimics SGFC's command line usage. This mode gives the library client no access to the actual SGF data. In this mode the library client can do the following:ISgfcDocumentReader
. The library uses SGFC for reading SGF data from a source specified by the library client, and makes that data available to the client for further processing. In this mode the library client can do the following:ISgfcDocument
).ISgfcDocumentWriter
. The library uses SGFC for writing SGF data provided by the library client to a destination also specified by the library client. In this mode the library client can do the following:ISgfcDocument
).In all of these modes the library client can specify arguments that indicate how SGFC should perform the read and/or write operations. The available arguments are a subset of the SGFC command line arguments. Certain arguments (e.g. --help
, -i
) are not available because they do not make sense in a library context, or because the library does not support them.
When SGFC reads or writes SGF data it often generates one or more messages. Every message has an ID, and every message ID is an indicator for what the message is about. libsgfc++ defines the enumeration SgfcMessageID
with all possible message IDs accompanied by sparse documentation. To see the full documentation consult the SGFC documentation where all messages are listed with their IDs and meanings.
The library processes the message data generated by SGFC and makes it available to the library client in the form of a collection of ISgfcMessage
objects. These objects provide the data in a structured form so that the library client can programmatically evaluate the message content and possibly handle certain warnings or errors.
Yes, the library does throw exceptions! The API documentation mentions all exceptions that can be thrown. The library client is responsible to handle the documented exceptions, or face the consequences. Any exceptions that are thrown are thrown by value so the library client can catch them by reference (e.g. catch (std::invalid_argument& e)
).
The library throws certain exceptions when the library client makes mistakes using the library API. Examples:
ISgfcCommandLine
object with invalid arguments and calls its LoadSgfFile
method without checking first whether the arguments were valid.nullptr
to SgfcPlusPlusFactory::CreateDocument
.The library may also throw std::domain_error
when an unexpected SGFC interfacing problem occurs. Although this may not be the fault of the library client, the design goal here is to fail fast and hard to find out about such problems sooner rather than later. If you encounter such a problem don't hesitate to file a bug report!
Full disclosure: The library can throw std::logic_error
when a library coding error occurs (e.g. an enumeration value was forgotten in a switch
statement). Obviously this should never happen, unit tests should have caught such problems before they library was released, etc. etc. Again, the design goal is to find these problems and not sweep them under the rug. Apologies in advance if it does happen, and please file a bug report.
When the library reads SGF data from a source, it makes that data available as an ISgfcDocument
. ISgfcDocument
objects can also be built programmatically from scratch using the library's factories.
Currently such a document is equivalent to what the SGF standard shows as a "collection of game trees" in its EBNF definition. In a future version of the library the document concept might be expanded. For instance it might be possible to add meta data to a document.
Documents can be written back as SGF data to a destination. Documents can also be validated by simulating a write operation - this can be useful for ISgfcDocument
objects that were built programmatically.
Because C++ does not have a dedicated interface feature, the library implementation is forced to employ multiple inheritance. The interface class ISgfcSinglePropertyValue
is inherited multiple times in all typed property value classes (e.g. SgfcColorPropertyValue
):
SgfcSinglePropertyValue
base classISgfcColorPropertyValue
)To make this work all sub-classes of ISgfcSinglePropertyValue
have to use virtual inheritance. This makes sure that objects contain only one shared ISgfcSinglePropertyValue
instance.
Without virtual inheritance a class such as SgfcColorPropertyValue
cannot be instantiated because the compiler sees it as abstract, because SgfcColorPropertyValue
does not implement all pure virtual methods that it inherits via the ISgfcColorPropertyValue
base class.
Other classes that also have to use virtual inheritance due to the same reasons:
ISgfcProperty
(because of typed property classes such as SgfcGameTypeProperty
)ISgfcMovePropertyValue
(because of SgfcGoMovePropertyValue
)ISgfcPointPropertyValue
(because of SgfcGoPointPropertyValue
)ISgfcStonePropertyValue
(because of SgfcGoStonePropertyValue
)IMPORTANT: Where virtual inheritance is in play downcasting must be done with dynamic_cast
or std::dynamic_pointer_cast
, NOT with static_cast
or std::static_pointer_cast
. An example: