-
Notifications
You must be signed in to change notification settings - Fork 3.2k
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
[Swift] Swift implementation 🎉🎉 #5603
Conversation
Can you add some tests that read the |
Definitely would do that, I just want to know if the |
Yep, |
Thanks, this is exciting! Some quick thoughts from your readme, and then an initial code review will follow:
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This generally looks great! It indeed looks like it mirrors the C++ (and C#) code closely, and I have very little to complain about. This looks like it would result in a pretty efficient Swift implementation, though I guess that remains to be tested.
So biggest thing right now would be an actual code generator to see how that would affect things.
@@ -0,0 +1,102 @@ | |||
<?xml version="1.0" encoding="UTF-8"?> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ideally we don't store IDE data in repo, not sure how unavoidable that is in the case of Swift. Does this code build from the command-line without XCode files?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe we can remove that indeed, however we will need a way to make SPM work since it would be a nested file. since it would be looking for the Package.swift, so I think we might need to make it a git submodule
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We currently don't have any submodules for this repo, so would be good to avoid starting that now :)
} | ||
} | ||
|
||
class Country { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This seems a new test case specific to Swift? Important that once we have codegen, that it uses the existing tests.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, all the test cases were writing manually for swift since we don't have a codegen. however they were all written and compared with both cpp
and C#
. Since you can see that all the test cases are compared by Bytes to an array
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
so? can these tests be removed now then? Or do you think they provide useful extra coverage?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it's nice to have them for extra coverage.
return o + _bb.read(def: Int32.self, position: Int(o)) + 4 | ||
} | ||
|
||
public func readBuffer<T: Scalar>(of type: T.Type, offset o: Int32) -> T { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I assume in Swift this T.Type
argument is fully static and does not involve any runtime value?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- A metatype type refers to the type of any type, including class types, structure types, enumeration types, and protocol types. https://docs.swift.org/swift-book/ReferenceManual/Types.html
This would request the codegen to specify the type of the readable object before reading it. As in __p.readBuffer(of: Double.self, offset: 10)
since Double confirms to the type Scalar
, however I am not sure if it would be fully static or not.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
On a side note, this is how Swift
and Apple
have implemented most of their methods, and it can be seen all over swift. so I think it would be pretty safe to use it when using the code generator, since users would be advised against changing it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just found a talk from apple that might actually help out https://developer.apple.com/videos/play/wwdc2016/416/
/// - Parameter initialSize: | ||
public init(initialSize: Int32 = 1024, serializeDefaults force: Bool = false) { | ||
guard initialSize > 0 else { fatalError(FlatbufferError.sizeIsZeroOrLess.errorDescription ?? "") } | ||
guard isLitteEndian else { fatalError(FlatbufferError.endianCheck.errorDescription ?? "") } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- Looking at the docs this is not clear, but I am guessing that
UnsafeMutableRawPointer
when it reads a 16/32/64-bit quantity, it will do so with machine-endianness ordering, which means it won't work correctly on big endian machines. This means you should either have explicit big-endian versions of most of these functions, or (and this is entirely acceptable) explicitly disable this code to run on big-endian.
Currently, indeed the code is disabled from running on Big-endian machines. In hopes that we would be implementing it whenever we get to have an issue regarding it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's fine.
The struct names can be modified, it won't be a problem, when we get to have the code generator. we can make the codegen create them as follows struct Vec3: Readable {}
struct Vec3Writer: Writeable {}
Yes, all the test cases were hard coded for the current tests, we wanted to get your opinion regarding the current implementation before going any further than what we currently have. |
@aardappel, @mzaks and I were implementing the test cases for the swift, that adhere to the test cases that the FlatBuffers repository has. However, we ran into an issue: as explained in following issue. the short explanation would be the current implementation fo the swift doesn't allow us to next objects into structs, or have different types which is solved easily using the following:
however as explained in the issue this will lead into a none zero padding, would that be an issue?
would result into the following Binary representation for cpp and c#: however since we are writing and reading from memory in swift for structs this might lead to something like this, where the padding between the floats and doubles would have |
@mustiikhalil you're saying Swift's padding can generate garbage data for padding? While I don't believe there is currently any code that expects padding to be 0, that is what all other languages write out, so Swift generating non-zero padding could cause problems for any code expecting binary equivalence of some sort. I'd say writing it in a way that guarantees zeroes is pretty important. Having garbage there also makes output non-deterministic, and it makes compression of serialized data worse. |
yeah it makes sense, it's just in the structs that we face this issue, we can find other ways to implement it and fix it |
@aardappel following up on yesterdays issue, I managed to implement the following fix for the structs, however I am not sure if you would approve it struct Vec3Writer: Writeable {
var memory: UnsafeMutableRawPointer
static var size = 32
static var alignment = 8
init(x: Float32, y: Float32, z: Float32, test1: Double, color: Color_1, test: Test_1) {
/// creates a memory that would store the struct in itself so it would be passed on later
memory = UnsafeMutableRawPointer.allocate(byteCount: Vec3Builder.size, alignment: Vec3Builder.alignment)
memory.initializeMemory(as: UInt8.self, repeating: 0, count: Vec3Builder.size)
memory.storeBytes(of: x, toByteOffset: 0, as: Float32.self)
memory.storeBytes(of: y, toByteOffset: 4, as: Float32.self)
memory.storeBytes(of: z, toByteOffset: 8, as: Float32.self)
memory.storeBytes(of: test1, toByteOffset: 16, as: Double.self)
memory.storeBytes(of: color.rawValue, toByteOffset: 24, as: Int8.self)
memory.storeBytes(of: 0, toByteOffset: 25, as: Int8.self)
memory.storeBytes(of: test.a, toByteOffset: 26, as: Int16.self)
memory.storeBytes(of: test.b, toByteOffset: 28, as: Int8.self)
}
}
///we create the struct as follows and then pass it to create struct
let v = Vec3Writer(x: 1, y: 2, z: 3, test1: 3, color: .green, test: Test_1(a: 5, b: 6))
fbb.create(struct: v)
/// this is the internal call for copying the memory of the current implemented struct
buffer.copyMemory(v.memory, count: size) also thought of passing the memory only to the FlatBufferBuilder but that would make it hard for us to follow with the size and the alignment of everything since we really depend on it |
I was also surprised to see non 0 padding, but it is how it is. Regarding deserialisation. If we are doing a value by value serialisation than it makes sense to have value by value deserialisation as otherwise we might hit problem (the byte array representing a struct being not compatible with Swift struct representation). |
Yes, most languages can't guarantee that their memory layout is the same as FlatBuffers, so should typically be done one by one. In C++ we have so macro trickery to check at compile time that the layout is the same, and so far we haven't found a compiler for which that fails, but if we did, we'd need per-field serialization even in C++. As for |
@aardappel we created it as a struct so that we would be able to insert structs as a vector, which would have the alignment of the memory and also the size of the memory stripe. since we don't want to have variables that are laying around, static var size = 32
static var alignment = 8 that's why we implemented it with a struct to contain the values within the data structure but other than the struct, does the implementation of the so technically this is how it would look like: func createVec3(x: Float32, y: Float32, z: Float32, test1: Double, color: Color_1, testA: Int16, testB: Int8) -> UnsafeMutableRawPointer {
let memory = UnsafeMutableRawPointer.allocate(byteCount: Vec3.size, alignment: Vec3.alignment)
memory.initializeMemory(as: UInt8.self, repeating: 0, count: Vec3.size)
memory.storeBytes(of: x, toByteOffset: 0, as: Float32.self)
memory.storeBytes(of: y, toByteOffset: 4, as: Float32.self)
memory.storeBytes(of: z, toByteOffset: 8, as: Float32.self)
memory.storeBytes(of: test1, toByteOffset: 16, as: Double.self)
memory.storeBytes(of: color.rawValue, toByteOffset: 24, as: Int8.self)
memory.storeBytes(of: 0, toByteOffset: 25, as: Int8.self)
memory.storeBytes(of: testA, toByteOffset: 26, as: Int16.self)
memory.storeBytes(of: testB, toByteOffset: 28, as: Int8.self)
return memory
}
struct Vec3: Readable {
static var size = 32
static var alignment = 8
init(_ bb: FlatBuffer, o: Int32) {}
/// reader code comes here
}
// and would be used as follows
fbb.create(struct: createVec3(x: 1, y: 2, z: 3, test1: 3, color: .green, testA: 5, testB: 6), type: Vec3.self) |
@aardappel currently @mzaks and I have almost finished everything regarding the swift implementation. we've implemented all the required APIs and also convenience APIs that would allow the codegen to implement the functions accordingly and without worrying about the index and positions of stuff. @dbaileychess as you requested earlier when we opened the PR the implementation of the test cases that will be reading from different languages, that is done using the what's missing:
|
@mustiikhalil does this have codegen now? |
@aardappel i just want a final review for the current swift APIs. so we start building the code gen. |
@mustiikhalil reviewing a PR this size is a lot of work, so I'd prefer doing that again once you feel you've done everything you can. Besides, API changes are probably even easier once you have code generation, and it is easier to see for me how it compares to the other languages when it is generating code for the exact same schemas and the same tests. |
289da3e
to
06ec08c
Compare
@aardappel and @mzaks I have a couple of questions regarding the code gen and would love to get your input regarding the matter. I've built like 90% of the code generator, however I am not sure how to handle the namespaces? we don't want people to keep typing what we are missing currently is the following in the code gen: do we need to implement the function to create an object in swift? |
Namespaces are a bit of a problem as Swift doesn't really have the concept. In Swift you either use Modules (but you can't have nested modules), or people also wrap definitions in empty enums, in order to mimic a name space. e.g.:
In order to avoid repeating your self with
Which is not ideal, but in case that your schema makes use of namespacing and it is possible that Swift is not the only language using this schema, I am afraid this is the only way to make thing correct.
Not sure I understand the question. |
There are some tables that would have a constructor that will hold all the values in place example: static func createMonster(builder: inout FlatBuffersBuilder,
position: Offset<UOffset>,
hp: Int16,
name: Offset<String>,
inventory: Offset<UOffset>,
color: Color3,
weapons: Offset<UOffset>,
equipment: Equipment = .none,
equippedOffset: Offset<Weapon>,
path: Offset<UOffset>) -> Offset<Monster> {
...
} are we required to have this in the initial release? @mzaks and if it's possible can you take a look at the code Gen? just so we know that it's good to go, since what's left is the lookup by key code, in swift and cpp |
Yes, use of namespaces should mimic the other languages, since we're not changing the schemas for Swift. To what extend is this enum trick standard? Should we use it? Since this is generated code, some verbosity is ok, so simply using full names everywhere should be ok. For the user, if there is no way to do "using namespace" in Swift, they are going to have to deal with the long names. We can't generate typealiases for them, since they may clash. Having the 1 call |
It's either we use Enums or structs for that and as @mzaks already suggested I think the enums will do the job, since apple does use it to nest classes in their |
@mustiikhalil sorry for being so unresponsive. Will try to go through the generated code this week. |
@aardappel and @mzaks I just finished the implementation for the code Gen, and I do apologize in advance if the cpp code isn't as you expect it to be, the code Gen generates the swift file by #if os(Linux)
import CoreFoundation
#else
import Foundation
#endif however the test cases, that are running on this PR are still failing, I am not sure why though |
@mustiikhalil to make the C++ code more conform, first step is to run And yes, would be good to have the tests pass first :) |
@aardappel and @mzaks I think this PR is finalized now, whenever @mzaks has time to review the swift code that would be amazing. I've already tested it on linux, macOS and iOS. I've fixed all the memory issues that we were facing. I've used Xcode's the following to debug the entire project: |
Generally thing already looks quite great. I have another few things, which are not blockers, but seem strange to me and could be addressed now or maybe in sub sequential releases:
All in all great job @mustiikhalil! |
One more thing which could make Swift support better and probably could benefit also other languages as well. Swift package manager works best if the git repos root contains the |
@mzaks so regarding the |
@mzaks would prefer to keep package file in subdir if at all possible. |
@aardappel, @mzaks fixed all the issues you mentioned yesterday: However, I didn't change the count for the unions arrays since all the languages do the same thing, so I kept that to adhere to the other APIs. and didn't touch the |
@aardappel as far as I can tell in order to make subtree work, we need something like following command:
To create a new branch |
@mustiikhalil one more small thing, you probably what to |
74a70f8
to
36a4ab6
Compare
@mzaks that's done :D so hopefully the PR looks clean now! and the swift implementation would be good to go! |
Implemented serailzing, reading, and mutating data from object monster Fixes mis-aligned pointer issue Fixes issue when shared strings are removed from table Adds swift enum, structs code gen Fixed namespace issues + started implementing the table gen Added Mutate function to the code generator Generated linux test cases Fixed an issue with bools, and structs readers in table writer Swift docker image added Updated the test cases, and removed a method parameters in swift Fixed createVector api when called with scalars Fixed issues with scalar arrays, and fixed the code gen namespaces, added sample_binary.swift Cleaned up project Added enum vectors, and their readers Refactored code Added swift into the support document Added documentation in docs, and fixed a small issue with Data() not being returned correctly Fixes Lowercase issue, and prevents generating lookups for deprecated keys
…ix lowercase func
…on for unions, and updated the names of the vector functions
36a4ab6
to
c94fadb
Compare
Yup, would totally be a fan of moving other language cruft into subdirs as well.. but that's probably for another PR. So.. ready for me to merge? |
i think so, unless @mzaks would say otherwise |
Ok, time to merge then! |
* Implemented the swift version of Flatbuffers Implemented serailzing, reading, and mutating data from object monster Fixes mis-aligned pointer issue Fixes issue when shared strings are removed from table Adds swift enum, structs code gen Fixed namespace issues + started implementing the table gen Added Mutate function to the code generator Generated linux test cases Fixed an issue with bools, and structs readers in table writer Swift docker image added Updated the test cases, and removed a method parameters in swift Fixed createVector api when called with scalars Fixed issues with scalar arrays, and fixed the code gen namespaces, added sample_binary.swift Cleaned up project Added enum vectors, and their readers Refactored code Added swift into the support document Added documentation in docs, and fixed a small issue with Data() not being returned correctly Fixes Lowercase issue, and prevents generating lookups for deprecated keys * Made all the required funcs to have const + removed unneeded code + fix lowercase func * Removed transform from lowercased and moved it to function * Fixes an issue with iOS allocation from read * Refactored cpp code to be more readable * casts position into int for position * Fix enums issue, moves scalar writer code to use memcpy * Removed c_str from struct function * Fixed script to generate new objects when ran on travis ci: fix * Handles deallocating space allocated for structs * Updated the test cases to adhere to the fileprivate lookup, no mutation for unions, and updated the names of the vector functions
* Implemented the swift version of Flatbuffers Implemented serailzing, reading, and mutating data from object monster Fixes mis-aligned pointer issue Fixes issue when shared strings are removed from table Adds swift enum, structs code gen Fixed namespace issues + started implementing the table gen Added Mutate function to the code generator Generated linux test cases Fixed an issue with bools, and structs readers in table writer Swift docker image added Updated the test cases, and removed a method parameters in swift Fixed createVector api when called with scalars Fixed issues with scalar arrays, and fixed the code gen namespaces, added sample_binary.swift Cleaned up project Added enum vectors, and their readers Refactored code Added swift into the support document Added documentation in docs, and fixed a small issue with Data() not being returned correctly Fixes Lowercase issue, and prevents generating lookups for deprecated keys * Made all the required funcs to have const + removed unneeded code + fix lowercase func * Removed transform from lowercased and moved it to function * Fixes an issue with iOS allocation from read * Refactored cpp code to be more readable * casts position into int for position * Fix enums issue, moves scalar writer code to use memcpy * Removed c_str from struct function * Fixed script to generate new objects when ran on travis ci: fix * Handles deallocating space allocated for structs * Updated the test cases to adhere to the fileprivate lookup, no mutation for unions, and updated the names of the vector functions
Good afternoon,
@mzaks and I hope with this PR we would be able to add an official implementation for swift, it's heavily inspired by the
C++
andC#
implementations. There are some elements that are still missing such as the code generator and also documentation for the code base, however I found that we can implement those after getting an initial review for the swift code base, and if it lives up to the standards.The
FlatBuffer
class uses apples underlayingUnsafeMutableRawPointer
which allows is to write directly to the memory, and lefts a lot of heavy weight from our shoulders, since this implementation comes which the following functionstoreBytes
with takes a value and it's Bytes representation and just adds it to the buffer.UnsafeMutableRawPointer
the name is a bit misleading since it's actually safe to use if everything writing the bytes is well structured. and since we don't really allow the user to get to the underlying adding and pushing to the buffer and only allowing them to do so through thebuilder
it would be completely safe to use it.The
FlatBuffersBuilder
class uses the same underlying logic asC++
andC#
, but unlikeC#
it relies on generics to actually add bytes and elements into the buffer. we took a different approach regarding adding the structs into the buffer, since swift uses thestoreBytes
to actually store the struct into the buffer, and sinceFlatBuffers
do not allow string inStructs
we took that into our advantage.the following struct will be able to be directly inserted into the buffer, and we will be implementing a reader struct that will read the Writable struct as shown below. This will have the same concept as the builder Objects in c++. The readers were implemented to follow the
C#
way of fetching elements from the buffer.All the test case are passing, and they were created by the
flatc
code generator for bothC++
andC#
, and the basic implementation of swift was verified against those two since the code generator isn't implemented yet.what's missing:
1- Code Generator
2- Documentation
3- Benchmarking
when merging this PR we would be closing the following issues, Closes #5504. and I will be rebasing the commit before merging for sure