Skip to content

Permission Groups#2587

Open
Wunka wants to merge 28 commits into
PixelGuys:masterfrom
Wunka:permisisonGroups
Open

Permission Groups#2587
Wunka wants to merge 28 commits into
PixelGuys:masterfrom
Wunka:permisisonGroups

Conversation

@Wunka
Copy link
Copy Markdown
Contributor

@Wunka Wunka commented Feb 23, 2026

Follow Up PR for #2530

This adds the rest from the permissionLayer PR which was split of. For Documentation see #2530

@Wunka Wunka marked this pull request as ready for review February 24, 2026 18:35
@BoySanic BoySanic moved this to High Priority in PRs to review Feb 26, 2026
Comment thread src/server/command/_list.zig Outdated
Comment thread src/server/command/permission/group.zig Outdated
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would suggest to add this to the group command instead, maybe /group <whitelist/blacklist> <add/remove> <groupName> <path>

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did /group <whitelist/blacklist> <groupName> <add/remove/[blank]> <path>
I switch groupName and add/remove/[blank] for easy looking up if [blank] is used. but If you really want the other way around I can look into that

Comment thread src/server/permission.zig
Comment thread src/server/server.zig Outdated
Comment thread src/server/permission.zig Outdated
}

pub fn getGroup(name: []const u8) error{GroupNotFound}!*PermissionGroup {
return groups.getPtr(name) orelse return error.GroupNotFound;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hashmaps don't have stable pointers

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added the assert but thats probaly not enough right? I am sorry I am not that knowledgeable to know what would be the best course of action here.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I finally brought myself back to this pr. I have changed the hashmap from StringHashMapUnmanaged(PermissionGroup) to StringHashMapUnmanaged(*PermissionGroup).
This should from my understanding fix the problem with the stable pointers

Comment thread src/server/world.zig Outdated
}

pub fn loadPermissionGroups(self: *ServerWorld) !void {
const path = std.fmt.allocPrint(main.stackAllocator.allocator, "saves/{s}/groups.zig.zon", .{self.path}) catch unreachable;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

before we put everything into a single file, I would like to at least see an estimation of how big the file could be on a large server (for comparison the survival playtest despite having a peak of only around 10 concurrent players has seen over 500 distinct accounts).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Its for me the question what these groups will be used for. If we say only for command permissions I don't see the file being big. But if we say that for #81 there will actually be a group for every block then the file could grow quite big.

But separating them opens other issues. Should we load all groups at the start of a world? Or should we only load the groups in use? (would prevent commands on them).

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For #81 you should be allowed to assign the same group to multiple blocks. But it will still likely end up at 1 group per player on average.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So you want seperate files correct? What is then the answer to to the my follow up question about when to load the zon then?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since that makes management easier, I think for now we can just load them all into memory.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

after finnally looking back at this. Where should I then save the currentId? in the world.zig.zon?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have fixed the problem with a metadata.zon file inisde the group folder.
The groups are now split into seperate files per group.

I do want to update the group commands a bit, but I will do that in a follow up PR (@ syntax, list subcommand to list all groups a player is member of etc)

@IntegratedQuantum IntegratedQuantum moved this from High Priority to In review in PRs to review Mar 2, 2026
Comment thread src/server/command/permission/group.zig Outdated
Comment thread src/server/command/permission/group.zig Outdated
Comment thread src/server/command/permission/group.zig Outdated
source.sendMessage("#ff0000Permission path {s} is not present inside group {s} permission {s}list", .{path, groupName, @tagName(listType)});
}
},
}
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Imo all code paths should end with return, to prevent mistakes in future, where behavior is by mistake added to multiple code paths. This makes sense because all code paths are mutually exclusive. Such change would also flatten the structure a little bit by moving else contents directly into function body. Definitely not critical tho.

Comment thread src/server/command/permission/group.zig Outdated
}
if (pathOp) |_op| {
switch (_op) {
.add => group.permissions.addPermission(listType, path),
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add feedback to all positive paths.
"Permission path added to group X" or alike.

Comment thread src/server/permission.zig Outdated
allocator.destroy(self);
}

pub fn hasPermission(self: *PermissionGroup, permissionPath: []const u8) Permissions.PermissionResult {
Copy link
Copy Markdown
Collaborator

@Argmaster Argmaster Apr 15, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is excessively descriptive. You have permission.zig PermissionGroup, addPermission and permissionPath. We get it, permissions, we don't need to repeat it 4 times unless there is ambiguity. If there is, we should be consistent, so you should savePermissionGroup instead of saveGroups.
But there isn't. It can be Group, hasPermission, path and it should be fine.

Comment thread src/server/permission.zig
while (it.next()) |group| {
const path = std.fmt.allocPrint(allocator.allocator, "{s}/{d}.zon", .{groupsPath, group.value_ptr.*.id}) catch unreachable;
defer allocator.free(path);
var groupZon: ZonElement = .initObject(allocator);
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Move into PermissionGroup next to fromZon as toZon

Comment thread src/server/server.zig Outdated

pub fn removeFromGroup(self: *User, groupName: []const u8) bool {
sync.threadContext.assertCorrectContext(.server);
const slice = (self.permissionGroups.getKeyPtr(groupName) orelse return false).*;
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Variable names should carry the logical meaning of their content, not the type they contain. Type system already describe contained type, you can retrieve it at any time using language server. Comptime type parameters are an exception, as zig doesn't have contracts/protocols/interfaces, but that's not what we have here.

After this lengthy explanation, please rename to... Idk, groupNamePtr? Not sure what it contains.

Comment thread src/server/world.zig Outdated
defer groupDir.close();
var iterator = groupDir.iterate();
while (try iterator.next()) |file| {
if (file.kind == .file and std.mem.endsWith(u8, file.name, ".zon")) {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would be better to make two guard clauses out of that condition.

Copy link
Copy Markdown
Member

@IntegratedQuantum IntegratedQuantum left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In order to keep the line changes down, could you please split this into 3 PRs:

  • one only adds the core groups system and saving/loading in the world
  • one adds the changes to the User
  • one adds the group command

Comment thread src/server/world.zig Outdated
if (file.kind == .file and std.mem.endsWith(u8, file.name, ".zon")) {
const zon = try groupDir.readToZon(main.stackAllocator, file.name);
defer zon.deinit(main.stackAllocator);
if (std.mem.eql(u8, file.name, "metadata.zon")) continue;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is stored outside the groups subdirectory, so no need to check for it here right?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no. its inside the groups folder.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems I saved it inside and tried to load it outside.... I don't know how i didn't see that. But I do want it to b inside the folder

Comment thread src/server/world.zig
try files.cubyzDir().writeZon(path, worldData);
}

pub fn loadPermissionGroups(_: *ServerWorld, dir: main.files.Dir) !void {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this function should also be in permission.zig.

Comment thread src/server/permission.zig

pub fn init(allocator: NeverFailingAllocator) *PermissionGroup {
sync.threadContext.assertCorrectContext(.server);
currentId += 1;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should immediately save the metadata file.

Comment thread src/server/permission.zig Outdated
try std.testing.expectError(error.GroupNotFound, getGroup("root"));
}

test "invalidGroupPermissionEmptyGroups" {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also test the success case at least once.

Comment thread src/server/world.zig
try self.saveWorldConfig();

try self.saveAllPlayers();
try self.savePermissionGroups();
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be done if and only if the data changed.

@Wunka
Copy link
Copy Markdown
Contributor Author

Wunka commented May 1, 2026

Ok I did a few changes, here is a summary:

  • for each group and the metadata zon exists now a bool which says if we need to save it. That means on saving only changed things are overwritten (means also we can save not only on closing of the world but also in between, but currently not done)
  • I noticed with the change to one file per group I never addressed deleting the file, so I introduced a queue for saving these removed groups (inspired by the userdeinitList) to make this work.
  • added more tests for success cases as asked by quantum
  • after making the most changes, I removed in commit 6d272bb and e82e54a the command and changes to the user, so that will be done in follow up PRs

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: In review

Development

Successfully merging this pull request may close these issues.

4 participants