Skip to content
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

Simplify and improve Rude. #1

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open

Simplify and improve Rude. #1

wants to merge 3 commits into from

Conversation

moritzuehling
Copy link

No description provided.

@moritzuehling moritzuehling self-assigned this Jan 18, 2017
@moritzuehling moritzuehling requested review from main-- and oberien and removed request for oberien January 18, 2017 01:48
{
await target.Kick(REASONS[rng.Next(REASONS.Length)]);
RudeStatus.Remove(actor.UserId);
Copy link
Contributor

Choose a reason for hiding this comment

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

When a user is kicked, he gets removed all his rudes? He should still join with Rüdiger and his correct cooldown.

Copy link
Author

Choose a reason for hiding this comment

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

No, I don't think this is correct. Getting kicked should reset you completely.

Copy link
Member

Choose a reason for hiding this comment

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

Perhaps we should do away with kicking entirely. Now that auto-reconnect is gone it can be really annoying (especially if it happens while you're afk and then you spend the rest of the day wondering why no one is there).

Copy link
Author

Choose a reason for hiding this comment

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

Nah, I think that if you are ruded four times, you should still get kicked. It means that people agree on it, so ... yeah. You have been warned.

Copy link
Member

@main-- main-- left a comment

Choose a reason for hiding this comment

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

You simplified things a lot, but I'm not sure this is the best approach here. Features you dropped (this delta is vs the old design by me, not what was actually implemented/tested):

  • Cooldown: As I already outline in a comment, cooldowns were quite an important feature and I feel like rude would be open to a lot of abuse if we actually were to drop it. The old implementation would even pick a random cooldown so you can't do it on a timer. If you try to rude someone else while your rude is still on cooldown, you rude yourself instead. Note that you can't normally rude yourself (your implementation allows that even if the bug I pointed out is fixed) because that would allow you to arbitrarily reset your rude level as you drop to zero whenever you get kicked. The punishment-rude doesn't do that. It's a lot of special cases, but I feel like in the end it all works out beautifully.
  • Independent duration: Your implementation refreshes the cooldown whenever one receives a new rude while I believe we planned on having the old one maintain durations independently for each rude. I realize that this makes the expiration timer slightly (but not excessively) more complicated, but I don't think our choice here should depend on what's easier to implement but rather what works better in practice. And I'm no longer sure which one that is.
  • Randomness: Everybody knows that random = fun. The old design would randomize not only cooldowns but rude durations as well.

@@ -8,208 +8,77 @@

namespace Rude
{
public static class LinqExtension
public class RudePlugin : PluginBase
Copy link
Member

Choose a reason for hiding this comment

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

No need to rename it to RudePlugin, especially since the file is still called Rude.cs.

if (target.UserId <= 0 || actor.UserId <= 0)
return;
EnsureRudeStatus(actor.UserId);
var rude = RudeStatus[actor.UserId];
Copy link
Member

Choose a reason for hiding this comment

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

This makes people always rude themselves instead of the user they selected.

Copy link
Author

Choose a reason for hiding this comment

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

... I'm an idiot. Well, that would be obvious if we had a debug-envrionment :D

var inRudes = GetActiveInRudes(userId).Count();
var reudigLevel = GetReudigLevel(userId);
return GetUsername(name, inRudes, reudigLevel);
if (!RudeStatus.ContainsKey(userId))
Copy link
Member

Choose a reason for hiding this comment

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

Braces please.

Copy link
Author

Choose a reason for hiding this comment

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

Yes

var reudigLevel = GetReudigLevel(userId);
return GetUsername(name, inRudes, reudigLevel);
if (!RudeStatus.ContainsKey(userId))
RudeStatus[userId] = new RudeStatus
Copy link
Member

Choose a reason for hiding this comment

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

Do you even thread safety? There is absolutely no synchronization here and this is a normal Dictionary.

Copy link
Author

@moritzuehling moritzuehling Jan 18, 2017

Choose a reason for hiding this comment

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

I hate multithreading. Why can't everything be as cool as JS?

{
return GetKickRudes(userId).Count();
}
// Remove potential old prefix
Copy link
Member

Choose a reason for hiding this comment

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

FYI this was meant to be a temporary workaround. Now that rude supports guests it's actually even broken because guests may choose a name that already has a rude-prefix.

Copy link
Author

Choose a reason for hiding this comment

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

Yeah, but at the moment I don't want to keep track of applied prefixes. It's on my todo-list though.

await Task.Delay(RudePlugin.RudeCooldown);
lock (RudeLock)
{
if (RudeIteration != currentRudeInteration)
Copy link
Member

Choose a reason for hiding this comment

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

Might be easier to just use a CancellationToken in Task.Delay instead of this version number. Has the added bonus of freeing this right away instead of once the timer expires.

Copy link
Author

Choose a reason for hiding this comment

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

Maybe, but meh. I changed this anyways.

return;

RudeLevel = RudeLevel - 1;
RudeIteration++;
Copy link
Member

Choose a reason for hiding this comment

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

Why use ++ but not --?


private static readonly List<RudeEntity> Rudes = new List<RudeEntity>();
private static readonly RandomNumberGenerator SecureRng = RandomNumberGenerator.Create();
public Dictionary<int, RudeStatus> RudeStatus = new Dictionary<int, RudeStatus>();
Copy link
Member

Choose a reason for hiding this comment

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

Why is this public?

Copy link
Author

Choose a reason for hiding this comment

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

No good reason.

if(count == 0)
return null;
private readonly string[] Prefixes = { "[Rude] ", "[Ruderer] ", "[Rüdiger] " };
public static readonly TimeSpan RudeCooldown = TimeSpan.FromMinutes(30);
Copy link
Member

Choose a reason for hiding this comment

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

FYI the concept of cooldowns in the previous implementation meant that a given user could only rude once every 15 mins or so. This is IMO a very desirable feature as it prevents you from doing things like instakicking somebody or mass-ruding everyone on the server.

Copy link
Contributor

Choose a reason for hiding this comment

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

The old cooldown was 1 minute. If you ruded someone having already ruded in the last minute you ruded yourself instead.

Copy link
Member

Choose a reason for hiding this comment

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

this delta is vs the old design by me, not what was actually implemented/tested

Copy link
Author

Choose a reason for hiding this comment

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

Yep, that feature should be added. Agree. Should be rather easy to implement, will do.

if (!RudeStatus.ContainsKey(userId))
RudeStatus[userId] = new RudeStatus
{
OnRudeLevelDecrease = async () =>
Copy link
Member

Choose a reason for hiding this comment

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

I don't like this. You're never actually using this abstraction - you're just using it as a crutch for your data structures. Design them properly (add userid and rudeplugin to rudestatus) and you can drop this entirely.

@moritzuehling
Copy link
Author

@main-- How about now?

public const double MinRudeTime = 45;
public const double MaxRudeTime = 120;
public const double MinRudeActionTimeout = 3;
public const double MaxRudeActionTimeout = 18;
Copy link
Member

Choose a reason for hiding this comment

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

Using TimeSpan here might make this a little more obvious.

private ConcurrentDictionary<int, RudeStatus> RudeStatus = new ConcurrentDictionary<int, RudeStatus>();
private ConcurrentDictionary<int, RudeStatus> RudeActorStatus = new ConcurrentDictionary<int, RudeStatus>();

private Random rand = new Random();
Copy link
Member

Choose a reason for hiding this comment

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

The primary reason why all code in fancyauth everywhere either instantiates a new Random for every method invocation or just uses System.Security.Cryptography.RandomNumberGenerator right away is that Random is not thread-safe!

(Also, is this really called rand? Please...)


private void EnsureRudeStatus(int userId)
{
if (!RudeStatus.ContainsKey(userId))
Copy link
Member

Choose a reason for hiding this comment

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

This check is pointless. Not only is it TOCTTOU-racy but it also accomplishes nothing at all - TryAdd already does this for you.

});
}

if (!RudeActorStatus.ContainsKey(userId))
Copy link
Member

Choose a reason for hiding this comment

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

same

await user.SaveChanges();
}

private void EnsureRudeStatus(int userId)
Copy link
Member

Choose a reason for hiding this comment

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

tbh this whole function is ill-designed

because let's face it are you ever going to call this and then not use a RudeStatus?

Just call GetOrAdd on both and return them as out parameters or something. AFAICS there is no removal code so it's no big deal but if there was any, you would have to do this because your code is racy (A:ensure, B:remove, A:use).

var rude = RudeStatus[target.UserId];
var rudeActor = RudeActorStatus[actor.UserId];

if (rudeActor.RudeLevel > 0)
Copy link
Member

@main-- main-- Jan 19, 2017

Choose a reason for hiding this comment

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

Did you even stop and think about this for a second? Because unless I'm dumb this is utterly broken: The first level here means that your rude is on cooldown and lasts for the cooldown period. But as implemented, attempting to rude with your rude on cooldown adds additional levels for the full rude duration as cooldown. Also this is completely independent and never actually shows up anywhere, wtf.

if (rude.RudeLevel > Prefixes.Length)
{
rude.Reset();
await target.Kick("You have been too rude.");
Copy link
Member

Choose a reason for hiding this comment

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

FYI in the context of guests this leaks memory as every new guest has a new userid. The best solution would be to write this to the database but in any case, guests' data has to be freed (for members keeping it is acceptable but still not pretty).

{
RudeActorStatus.TryAdd(userId, new RudeStatus
{
OnRudeLevelDecrease = () => { }
Copy link
Member

Choose a reason for hiding this comment

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

My opinion stands. Reusing this data structure for cooldowns feels very bolted-on so this callback still carries no value IMO. The class's one big feature is the delay mechanism where you can run code after a timeout and abstracting that might even make sense if you do it on a general layer (where it's no longer specific to rude) but that still doesn't give you a valid reason to use it here and without that, it's kinda pointless.

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

Successfully merging this pull request may close these issues.

3 participants